diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..89f3cda --- /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: 'bug' +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 0000000..0436a85 --- /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 0000000..7d33975 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_request.md @@ -0,0 +1,17 @@ +--- +name: Documentation request +about: Request improved documentation +title: '' +labels: 'documentation' +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 0000000..6aaa792 --- /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: 'feature' +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. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..04595ae --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# 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" + commit-message: + prefix: infra diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..8fe8329 --- /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/amazon-braket/amazon-braket-simulator-v2-python/blob/main/CONTRIBUTING.md) doc +- [ ] I used the commit message format described in [CONTRIBUTING](https://github.com/amazon-braket/amazon-braket-simulator-v2-python/blob/main/CONTRIBUTING.md#commit-your-change) +- [ ] I have updated any necessary documentation, including [READMEs](https://github.com/amazon-braket/amazon-braket-simulator-v2-python/blob/main/README.md) and [API docs](https://github.com/amazon-braket/amazon-braket-simulator-v2-python/blob/main/CONTRIBUTING.md#documentation-guidelines) (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. diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml new file mode 100644 index 0000000..cbd77d2 --- /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@v4 + - name: Set up Python + uses: actions/setup-python@v5 + 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/code-freeze.yml b/.github/workflows/code-freeze.yml new file mode 100644 index 0000000..d19f5b4 --- /dev/null +++ b/.github/workflows/code-freeze.yml @@ -0,0 +1,49 @@ +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 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 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..a9fc30c --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,34 @@ +# 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, feature/** ] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install tox + - name: Run unit tests + run: | + tox -e unit-tests + - name: Upload coverage report to Codecov + uses: codecov/codecov-action@v3 + if: ${{ strategy.job-index }} == 0 diff --git a/.github/workflows/stale_issue.yml b/.github/workflows/stale_issue.yml new file mode 100644 index 0000000..482c708 --- /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@v6 + 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 diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml new file mode 100644 index 0000000..10c2882 --- /dev/null +++ b/.github/workflows/twine-check.yml @@ -0,0 +1,31 @@ +name: Check long description for PyPI + +on: + pull_request: + branches: + - main + - feature/** + +permissions: + contents: read + +jobs: + twine-check: + name: Check long description + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + 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: 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. + run: twine check dist/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b56787 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +*~ +*# +*.swp +*.idea +*.iml +build_files.tar.gz + +.ycm_extra_conf.py +.tox +.python-version + +__pycache__/ +*.py[cod] +*$py.class +*.egg-info/ +*.ipynb_checkpoints/ +pip-wheel-metadata/ + +/.coverage +/.coverage.* +/.cache +/.pytest_cache +/.mypy_cache + +/doc/_apidoc/ +/build +/venv +/dist +*.DS_Store diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..df13e06 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,6 @@ +# 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 for review when someone opens a pull request. +* @amazon-braket/braket-maintainers diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4b6a1c..1ca0488 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,53 +7,196 @@ 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 Performance Tests](#run-the-performance-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/amazon-braket/amazon-braket-simulator-v2-python/issues) and [recently closed](https://github.com/amazon-braket/amazon-braket-simulator-v2-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 *main* 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-simulator-v2-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-simulator-v2-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-simulator-v2-python folder: `cd amazon-braket-simulator-v2-python` or `cd /environment/amazon-braket-simulator-v2-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 Performance Tests + +To run the performance tests: +```bash +tox -e performance-tests +``` +These tests will compare the performance of a series of simulator executions for your changes against the latest commit on the main branch. +*Note*: The execution times for the performance tests are affected by the other processes running on the system. + + +### 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 documentation on [Creating a Pull Request](https://help.github.com/articles/creating-a-pull-request/). + +Please remember to: +* Use commit messages (and PR titles) that follow the guidelines under [Commit Your Change](#commit-your-change). +* Send us a pull request, answering any default questions in the pull request interface. +* Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. + + +## 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. + +### 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/amazon-braket/amazon-braket-simulator-v2-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/amazon-braket/amazon-braket-simulator-v2-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/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..d3dab47 --- /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 = amazon-braket-simulator-v2 +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 0000000..5649d7e --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,53 @@ +"""Sphinx configuration.""" + +import datetime + +import pkg_resources + +# Sphinx configuration below. +project = "amazon-braket-simulator-v2" +version = pkg_resources.require(project)[0].version +release = version +copyright = "{}, Amazon.com".format(datetime.datetime.now().year) + +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 = "{}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"] + + +# -- Options for MathJax output ------------------------------------------- + +mathjax_config = { + "TeX": { + "Macros": { + "bra": [r"{\langle #1 |}", 1], + "ket": [r"{| #1 \rangle}", 1], + "expectation": [r"{\langle #1 \rangle_#2}", 2], + "variance": [r"{\mathrm{Var}_#2 \left( #1 \right)}", 2], + } + } +} diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..970c871 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,17 @@ +Amazon Braket Simulator V2 Python +====================================== + +The Amazon Braket Simulator V2 is a Python open source library that provides an implementation of a quantum simulator +for state vectors and density matrices that you can run locally. + +Here you'll find an overview and API documentation for Amazon Braket Default Simulator Python. +The project homepage is in GitHub, https://github.com/amazon-braket/amazon-braket-simulator-v2-python, +where you can find the source and installation instructions for the library. + +Indices and tables +__________________ + +* :doc:`_apidoc/modules` +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 0000000..b80e6ba --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,3 @@ +sphinx>7 +sphinx-rtd-theme>=1.3.0 +sphinxcontrib-apidoc diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0020ec7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,53 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "amazon-braket-simulator-v2" +version = "0.0.1" +authors = [ + {name = "Katharine Hyatt", email = "hyatkath@amazon.com"}, +] +description = "Local simulation of quantum circuits" +readme = {file = "README.md", content-type = "text/markdown"} +license = {file = "LICENSE"} +requires-python = ">=3.10" +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + "License :: OSI Approved :: Apache Software License" +] +dynamic = ["dependencies"] + +[project.entry-points."braket.simulators"] +braket_sv_v2 = "braket.simulator_v2.simulator:StateVectorSimulatorV2" +braket_dm_v2 = "braket.simulator_v2.simulator:DensityMatrixSimulatorV2" + +[project.optional-dependencies] +test = [ + "black", + "flake8", + "flake8-rst-docstrings", + "isort", + "pre-commit", + "pylint", + "pytest==7.1.2", + "pytest-benchmark", + "pytest-cov", + "pytest-rerunfailures", + "pytest-xdist", + "sphinx", + "sphinx-rtd-theme", + "sphinxcontrib-apidoc", + "tox" +] + +[tool.setuptools] +include-package-data=false +package-data = {"*" = ["*.json"]} + +[tool.setuptools.dynamic] +dependencies = {file = "requirements.txt"} + +[tool.isort] +profile = "black" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8bd88f9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +juliacall==0.9.19 +numpy +amazon-braket-default-simulator>=1.21.2 +amazon-braket-schemas>=1.20.2 +amazon-braket-sdk>=1.76.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..cdce84f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,35 @@ +[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 +profile=black + +[flake8] +ignore = + # not pep8, black adds whitespace before ':' + E203, + # not pep8, black adds line break before binary operator + W503, + # Google Python style is not RST until after processed by Napoleon + # See https://github.com/peterjc/flake8-rst-docstrings/issues/17 + RST201,RST203,RST301, +max_line_length = 100 +max-complexity = 10 +exclude = + __pycache__ + .tox + .git + bin + dist + examples + build + venv diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3118db2 --- /dev/null +++ b/setup.py @@ -0,0 +1,32 @@ +import os + +from setuptools import setup + +if os.path.exists(".git"): + kwargs = { + "use_scm_version": { + "write_to": "src/braket/version.py", + }, + "setup_requires": ["setuptools", "setuptools_scm"], + } +else: + # Read from pyproject.toml directly + import re + + with open(os.path.join(os.path.dirname(__file__), "pyproject.toml")) as f: + data = f.read() + # Find the version + version = re.search(r'version = "(.*)"', data).group(1) + + # Write the version to version.py + with open( + os.path.join(os.path.dirname(__file__), "src", "braket", "version.py"), + "w", + ) as f: + f.write(f'__version__ = "{version}"') + + kwargs = {"version": version} + + +# Build options are managed in pyproject.toml +setup(**kwargs) diff --git a/src/braket/juliapkg.json b/src/braket/juliapkg.json new file mode 100644 index 0000000..43ab982 --- /dev/null +++ b/src/braket/juliapkg.json @@ -0,0 +1,21 @@ +{ + "julia": "1.9", + "packages": { + "BraketSimulator": { + "uuid": "76d27892-9a0b-406c-98e4-7c178e9b3dff", + "rev": "main" + }, + "Braket": { + "uuid": "19504a0f-b47d-4348-9127-acc6cc69ef67", + "rev": "ksh/oq3" + }, + "OpenQASM3": { + "uuid": "791dba98-90c7-4903-ae5f-ea3dc22ea9ce", + "rev": "ksh/parameters_update" + }, + "Antlr4Runtime": { + "uuid": "780548eb-20d3-4d14-b715-e70121e066d1", + "rev": "main" + } + } +} diff --git a/src/braket/simulator_v2/__init__.py b/src/braket/simulator_v2/__init__.py new file mode 100644 index 0000000..195ade0 --- /dev/null +++ b/src/braket/simulator_v2/__init__.py @@ -0,0 +1,20 @@ +# This must be imported as early as possible to prevent +# library linking issues caused by numpy/pytorch/etc. importing +# old libraries: +from .julia_import import jl, jlBraketSimulator # isort:skip +from .simulator import ( # isort:skip + DensityMatrixSimulatorV2, + StateVectorSimulatorV2, +) + +# This file is created by setuptools_scm during the build process: +from .version import __version__ + +__all__ = [ + "jl", + "StateVectorSimulatorV2", + "DensityMatrixSimulatorV2", + "jlBraketSimulator", + "simulator", + "__version__", +] diff --git a/src/braket/simulator_v2/julia_import.py b/src/braket/simulator_v2/julia_import.py new file mode 100644 index 0000000..610e4d7 --- /dev/null +++ b/src/braket/simulator_v2/julia_import.py @@ -0,0 +1,45 @@ +import os +import sys +import warnings + +# Check if JuliaCall is already loaded, and if so, warn the user +# about the relevant environment variables. If not loaded, +# set up sensible defaults. +if "juliacall" in sys.modules: + warnings.warn( + "`juliacall` module has already been imported. " + + "Make sure that you have set the environment variable " + + "`PYTHON_JULIACALL_HANDLE_SIGNALS=yes` to avoid segfaults. " + ) +else: + # Required to avoid segfaults (https://juliapy.github.io/PythonCall.jl/dev/faq/) + if os.environ.get("PYTHON_JULIACALL_HANDLE_SIGNALS", "yes") != "yes": + warnings.warn( + "`PYTHON_JULIACALL_HANDLE_SIGNALS` environment variable " + + "is set to something other than 'yes' or ''. " + + +"You will experience segfaults if running with Julia multithreading." + ) + + if os.environ.get("PYTHON_JULIACALL_THREADS", "auto") != "auto": + warnings.warn( + "`PYTHON_JULIACALL_THREADS` environment variable is set to " + + "something other than `auto`, so `amazon-braket-simulator-v2` " + + "was not able to set it." + ) + + for k, default in ( + ("PYTHON_JULIACALL_HANDLE_SIGNALS", "yes"), + ("PYTHON_JULIACALL_THREADS", "auto"), + ("PYTHON_JULIACALL_OPTLEVEL", "3"), + # let the user's Conda/Pip handle installing things + ("JULIA_CONDAPKG_BACKEND", "Null"), + ): + os.environ[k] = os.environ.get(k, default) + +import juliacall + +jl = juliacall.Base.Module() + +jl.seval("using PythonCall, Braket, BraketSimulator") +jl.seval("using PythonCall: Py, pyconvert") +jlBraketSimulator = jl.BraketSimulator diff --git a/src/braket/simulator_v2/simulator.py b/src/braket/simulator_v2/simulator.py new file mode 100644 index 0000000..cd4f5e2 --- /dev/null +++ b/src/braket/simulator_v2/simulator.py @@ -0,0 +1,782 @@ +import sys + +from braket.default_simulator.operation_helpers import from_braket_instruction +from braket.default_simulator.result_types import TargetedResultType +from braket.default_simulator.simulator import BaseLocalSimulator +from braket.device_schema import DeviceActionType +from braket.device_schema.simulators import ( + GateModelSimulatorDeviceCapabilities, + GateModelSimulatorDeviceParameters, +) +from braket.ir.jaqcd import Program as JaqcdProgram +from braket.ir.openqasm import Program as OpenQASMProgram +from braket.task_result import GateModelTaskResult + +from .julia_import import jl, jlBraketSimulator + + +class StateVectorSimulatorV2(BaseLocalSimulator): + DEVICE_ID = "braket_sv_v2" + _device = jlBraketSimulator.StateVectorSimulator(0, 0) + + def initialize_simulation(self, **kwargs): + return + + """A simulator meant to run directly on the user's machine using a Julia backend. + + 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 run_jaqcd( + self, + circuit_ir: JaqcdProgram, + qubit_count: int, + shots: int = 0, + batch_size: int = 1, # unused + ) -> GateModelTaskResult: + """Executes the circuit specified by the supplied `circuit_ir` on the simulator. + + Args: + circuit_ir (Program): ir representation of a braket circuit specifying the + instructions to execute. + qubit_count (int): The number of qubits to simulate. + shots (int): The number of times to run the circuit. + + Returns: + GateModelTaskResult: object that represents the result + + Raises: + ValueError: If result types are not specified in the IR or sample is specified + as a result type when shots=0. Or, if StateVector and Amplitude result types + are requested when shots>0. + """ + self._validate_ir_results_compatibility( + circuit_ir.results, + device_action_type=DeviceActionType.JAQCD, + ) + self._validate_ir_instructions_compatibility( + circuit_ir, + device_action_type=DeviceActionType.JAQCD, + ) + BaseLocalSimulator._validate_shots_and_ir_results( + shots, circuit_ir.results, qubit_count + ) + + operations = [ + from_braket_instruction(instruction) + for instruction in circuit_ir.instructions + ] + + if shots > 0 and circuit_ir.basis_rotation_instructions: + for instruction in circuit_ir.basis_rotation_instructions: + operations.append(from_braket_instruction(instruction)) + + BaseLocalSimulator._validate_operation_qubits(operations) + + if not shots and circuit_ir.results: + result_types = BaseLocalSimulator._translate_result_types( + circuit_ir.results + ) + BaseLocalSimulator._validate_result_types_qubits_exist( + [ + result_type + for result_type in result_types + if isinstance(result_type, TargetedResultType) + ], + qubit_count, + ) + r = jl.simulate(self._device, [circuit_ir], qubit_count, shots) + r.additionalMetadata.action = circuit_ir + return r + + def run_openqasm( + self, + openqasm_ir: OpenQASMProgram, + shots: int = 0, + batch_size: int = 1, # unused + ) -> GateModelTaskResult: + """Executes the circuit specified by the supplied `openqasm_ir` on the simulator. + + Args: + openqasm_ir (Program): ir representation of a braket circuit specifying the + instructions to execute. + shots (int): The number of times to run the circuit. + + Returns: + GateModelTaskResult: object that represents the result + + Raises: + ValueError: If result types are not specified in the IR or sample is specified + as a result type when shots=0. Or, if StateVector and Amplitude result types + are requested when shots>0. + """ + + # use Python parser + circuit = self.parse_program(openqasm_ir).circuit + qubit_count = circuit.num_qubits + measured_qubits = circuit.measured_qubits + + self._validate_ir_results_compatibility( + circuit.results, + device_action_type=DeviceActionType.OPENQASM, + ) + self._validate_ir_instructions_compatibility( + circuit, + device_action_type=DeviceActionType.OPENQASM, + ) + self._validate_input_provided(circuit) + BaseLocalSimulator._validate_shots_and_ir_results( + shots, circuit.results, qubit_count + ) + + operations = circuit.instructions + BaseLocalSimulator._validate_operation_qubits(operations) + + results = circuit.results + + if not shots: + result_types = BaseLocalSimulator._translate_result_types(results) + BaseLocalSimulator._validate_result_types_qubits_exist( + [ + result_type + for result_type in result_types + if isinstance(result_type, TargetedResultType) + ], + qubit_count, + ) + else: + for bri in circuit.basis_rotation_instructions: + circuit.add_instruction(bri) + + r = jl.simulate( + self._device, [circuit], qubit_count, shots, measured_qubits=measured_qubits + ) + r.additionalMetadata.action = openqasm_ir + # attach the result types + if shots: + r.resultTypes = results + return r + + @property + def properties(self) -> GateModelSimulatorDeviceCapabilities: + """ + Device properties for the StateVectorSimulator. + + Returns: + GateModelSimulatorDeviceCapabilities: Device capabilities for this simulator. + """ + observables = ["x", "y", "z", "h", "i", "hermitian"] + max_shots = sys.maxsize + qubit_count = 32 + return GateModelSimulatorDeviceCapabilities.parse_obj( + { + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "00:00", + "windowEndHour": "23:59:59", + } + ], + "shotsRange": [0, max_shots], + }, + "action": { + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": [ + "ccnot", + "cnot", + "cphaseshift", + "cphaseshift00", + "cphaseshift01", + "cphaseshift10", + "cswap", + "cv", + "cy", + "cz", + "ecr", + "h", + "i", + "iswap", + "pswap", + "phaseshift", + "rx", + "ry", + "rz", + "s", + "si", + "swap", + "t", + "ti", + "unitary", + "v", + "vi", + "x", + "xx", + "xy", + "y", + "yy", + "z", + "zz", + ], + "supportedResultTypes": [ + { + "name": "Sample", + "observables": observables, + "minShots": 1, + "maxShots": max_shots, + }, + { + "name": "Expectation", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Variance", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Probability", + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "StateVector", + "minShots": 0, + "maxShots": 0, + }, + { + "name": "DensityMatrix", + "minShots": 0, + "maxShots": 0, + }, + { + "name": "Amplitude", + "minShots": 0, + "maxShots": 0, + }, + ], + }, + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": [ + # OpenQASM primitives + "U", + "GPhase", + # builtin Braket gates + "ccnot", + "cnot", + "cphaseshift", + "cphaseshift00", + "cphaseshift01", + "cphaseshift10", + "cswap", + "cv", + "cy", + "cz", + "ecr", + "gpi", + "gpi2", + "h", + "i", + "iswap", + "ms", + "pswap", + "phaseshift", + "rx", + "ry", + "rz", + "s", + "si", + "swap", + "t", + "ti", + "unitary", + "v", + "vi", + "x", + "xx", + "xy", + "y", + "yy", + "z", + "zz", + ], + "supportedModifiers": [ + { + "name": "ctrl", + }, + { + "name": "negctrl", + }, + { + "name": "pow", + "exponent_types": ["int", "float"], + }, + { + "name": "inv", + }, + ], + "supportedPragmas": [ + "braket_unitary_matrix", + "braket_result_type_state_vector", + "braket_result_type_density_matrix", + "braket_result_type_sample", + "braket_result_type_expectation", + "braket_result_type_variance", + "braket_result_type_probability", + "braket_result_type_amplitude", + ], + "forbiddenPragmas": [ + "braket_noise_amplitude_damping", + "braket_noise_bit_flip", + "braket_noise_depolarizing", + "braket_noise_kraus", + "braket_noise_pauli_channel", + "braket_noise_generalized_amplitude_damping", + "braket_noise_phase_flip", + "braket_noise_phase_damping", + "braket_noise_two_qubit_dephasing", + "braket_noise_two_qubit_depolarizing", + "braket_result_type_adjoint_gradient", + ], + "supportedResultTypes": [ + { + "name": "Sample", + "observables": observables, + "minShots": 1, + "maxShots": max_shots, + }, + { + "name": "Expectation", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Variance", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Probability", + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "StateVector", + "minShots": 0, + "maxShots": 0, + }, + { + "name": "DensityMatrix", + "minShots": 0, + "maxShots": 0, + }, + { + "name": "Amplitude", + "minShots": 0, + "maxShots": 0, + }, + ], + "supportPhysicalQubits": False, + "supportsPartialVerbatimBox": False, + "requiresContiguousQubitIndices": True, + "requiresAllQubitsMeasurement": True, + "supportsUnassignedMeasurements": True, + "disabledQubitRewiringSupported": False, + }, + }, + "paradigm": {"qubitCount": qubit_count}, + "deviceParameters": GateModelSimulatorDeviceParameters.schema(), + } + ) + + +class DensityMatrixSimulatorV2(BaseLocalSimulator): + DEVICE_ID = "braket_dm_v2" + _device = jlBraketSimulator.DensityMatrixSimulator(0, 0) + + def initialize_simulation(self, **kwargs): + return + + def run_jaqcd( + self, + circuit_ir: JaqcdProgram, + qubit_count: int, + shots: int = 0, + batch_size: int = 1, # unused + ) -> GateModelTaskResult: + """Executes the circuit specified by the supplied `circuit_ir` on the simulator. + + Args: + circuit_ir (Program): ir representation of a braket circuit specifying the + instructions to execute. + qubit_count (int): The number of qubits to simulate. + shots (int): The number of times to run the circuit. + + Returns: + GateModelTaskResult: object that represents the result + + Raises: + ValueError: If result types are not specified in the IR or sample is specified + as a result type when shots=0. Or, if StateVector and Amplitude result types + are requested when shots>0. + """ + self._validate_ir_results_compatibility( + circuit_ir.results, + device_action_type=DeviceActionType.JAQCD, + ) + self._validate_ir_instructions_compatibility( + circuit_ir, + device_action_type=DeviceActionType.JAQCD, + ) + BaseLocalSimulator._validate_shots_and_ir_results( + shots, circuit_ir.results, qubit_count + ) + + operations = [ + from_braket_instruction(instruction) + for instruction in circuit_ir.instructions + ] + + if shots > 0 and circuit_ir.basis_rotation_instructions: + for instruction in circuit_ir.basis_rotation_instructions: + operations.append(from_braket_instruction(instruction)) + + BaseLocalSimulator._validate_operation_qubits(operations) + + if not shots and circuit_ir.results: + result_types = BaseLocalSimulator._translate_result_types( + circuit_ir.results + ) + BaseLocalSimulator._validate_result_types_qubits_exist( + [ + result_type + for result_type in result_types + if isinstance(result_type, TargetedResultType) + ], + qubit_count, + ) + r = jl.simulate(self._device, [circuit_ir], qubit_count, shots) + r.additionalMetadata.action = circuit_ir + return r + + def run_openqasm( + self, + openqasm_ir: OpenQASMProgram, + shots: int = 0, + batch_size: int = 1, # unused + ) -> GateModelTaskResult: + """Executes the circuit specified by the supplied `openqasm_ir` on the simulator. + + Args: + openqasm_ir (Program): ir representation of a braket circuit specifying the + instructions to execute. + shots (int): The number of times to run the circuit. + + Returns: + GateModelTaskResult: object that represents the result + + Raises: + ValueError: If result types are not specified in the IR or sample is specified + as a result type when shots=0. Or, if StateVector and Amplitude result types + are requested when shots>0. + """ + + # use Python parser + circuit = self.parse_program(openqasm_ir).circuit + qubit_count = circuit.num_qubits + measured_qubits = circuit.measured_qubits + + self._validate_ir_results_compatibility( + circuit.results, + device_action_type=DeviceActionType.OPENQASM, + ) + self._validate_ir_instructions_compatibility( + circuit, + device_action_type=DeviceActionType.OPENQASM, + ) + self._validate_input_provided(circuit) + BaseLocalSimulator._validate_shots_and_ir_results( + shots, circuit.results, qubit_count + ) + + operations = circuit.instructions + BaseLocalSimulator._validate_operation_qubits(operations) + + results = circuit.results + if not shots: + result_types = BaseLocalSimulator._translate_result_types(results) + BaseLocalSimulator._validate_result_types_qubits_exist( + [ + result_type + for result_type in result_types + if isinstance(result_type, TargetedResultType) + ], + qubit_count, + ) + else: + for bri in circuit.basis_rotation_instructions: + circuit.add_instruction(bri) + + r = jl.simulate( + self._device, [circuit], qubit_count, shots, measured_qubits=measured_qubits + ) + r.additionalMetadata.action = openqasm_ir + # attach the result types + if shots: + r.resultTypes = results + return r + + """A simulator meant to run directly on the user's machine using a Julia backend. + + This class wraps a BraketSimulator object so that it can be run and returns + results using constructs from the SDK rather than Braket IR. + """ + + @property + def properties(self) -> GateModelSimulatorDeviceCapabilities: + observables = ["x", "y", "z", "h", "i", "hermitian"] + max_shots = sys.maxsize + qubit_count = 16 + return GateModelSimulatorDeviceCapabilities.parse_obj( + { + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "00:00", + "windowEndHour": "23:59:59", + } + ], + "shotsRange": [0, max_shots], + }, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": sorted( + [ + # OpenQASM primitives + "U", + "GPhase", + # builtin Braket gates + "ccnot", + "cnot", + "cphaseshift", + "cphaseshift00", + "cphaseshift01", + "cphaseshift10", + "cswap", + "cv", + "cy", + "cz", + "ecr", + "gpi", + "gpi2", + "h", + "i", + "iswap", + "ms", + "pswap", + "phaseshift", + "rx", + "ry", + "rz", + "s", + "si", + "swap", + "t", + "ti", + "unitary", + "v", + "vi", + "x", + "xx", + "xy", + "y", + "yy", + "z", + "zz", + # noise operations + "bit_flip", + "phase_flip", + "pauli_channel", + "depolarizing", + "two_qubit_depolarizing", + "two_qubit_dephasing", + "amplitude_damping", + "generalized_amplitude_damping", + "phase_damping", + "kraus", + ] + ), + "supportedModifiers": [ + { + "name": "ctrl", + }, + { + "name": "negctrl", + }, + { + "name": "pow", + "exponent_types": ["int", "float"], + }, + { + "name": "inv", + }, + ], + "supportedPragmas": [ + "braket_unitary_matrix", + "braket_result_type_state_vector", + "braket_result_type_density_matrix", + "braket_result_type_sample", + "braket_result_type_expectation", + "braket_result_type_variance", + "braket_result_type_probability", + "braket_result_type_amplitude", + "braket_noise_amplitude_damping", + "braket_noise_bit_flip", + "braket_noise_depolarizing", + "braket_noise_kraus", + "braket_noise_pauli_channel", + "braket_noise_generalized_amplitude_damping", + "braket_noise_phase_flip", + "braket_noise_phase_damping", + "braket_noise_two_qubit_dephasing", + "braket_noise_two_qubit_depolarizing", + ], + "forbiddenPragmas": [ + "braket_result_type_adjoint_gradient", + ], + "supportedResultTypes": [ + { + "name": "Sample", + "observables": observables, + "minShots": 1, + "maxShots": max_shots, + }, + { + "name": "Expectation", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Variance", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Probability", + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "DensityMatrix", + "minShots": 0, + "maxShots": 0, + }, + ], + "supportPhysicalQubits": False, + "supportsPartialVerbatimBox": False, + "requiresContiguousQubitIndices": True, + "requiresAllQubitsMeasurement": True, + "supportsUnassignedMeasurements": True, + "disabledQubitRewiringSupported": False, + }, + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": [ + "amplitude_damping", + "bit_flip", + "ccnot", + "cnot", + "cphaseshift", + "cphaseshift00", + "cphaseshift01", + "cphaseshift10", + "cswap", + "cv", + "cy", + "cz", + "depolarizing", + "ecr", + "generalized_amplitude_damping", + "h", + "i", + "iswap", + "kraus", + "pauli_channel", + "two_qubit_pauli_channel", + "phase_flip", + "phase_damping", + "phaseshift", + "pswap", + "rx", + "ry", + "rz", + "s", + "si", + "swap", + "t", + "ti", + "two_qubit_dephasing", + "two_qubit_depolarizing", + "unitary", + "v", + "vi", + "x", + "xx", + "xy", + "y", + "yy", + "z", + "zz", + ], + "supportedResultTypes": [ + { + "name": "Sample", + "observables": observables, + "minShots": 1, + "maxShots": max_shots, + }, + { + "name": "Expectation", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Variance", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Probability", + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "DensityMatrix", + "minShots": 0, + "maxShots": 0, + }, + ], + }, + }, + "paradigm": {"qubitCount": qubit_count}, + "deviceParameters": GateModelSimulatorDeviceParameters.schema(), + } + ) diff --git a/src/braket/simulator_v2/version.py b/src/braket/simulator_v2/version.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/src/braket/simulator_v2/version.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/src/braket/version.py b/src/braket/version.py new file mode 100644 index 0000000..c3100cd --- /dev/null +++ b/src/braket/version.py @@ -0,0 +1,16 @@ +# file generated by setuptools_scm +# don't change, don't track in version control +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple, Union + VERSION_TUPLE = Tuple[Union[int, str], ...] +else: + VERSION_TUPLE = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE + +__version__ = version = '0.1.dev36+g39f25c0.d20240410' +__version_tuple__ = version_tuple = (0, 1, 'dev36', 'g39f25c0.d20240410') diff --git a/test/integ_tests/braket/simulator_v2/gate_model_device_testing_utils.py b/test/integ_tests/braket/simulator_v2/gate_model_device_testing_utils.py new file mode 100644 index 0000000..a580db5 --- /dev/null +++ b/test/integ_tests/braket/simulator_v2/gate_model_device_testing_utils.py @@ -0,0 +1,801 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT 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, Dict, 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,) + 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], 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,) + 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: + 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/braket/simulator_v2/test_local_braket_simulator.py b/test/integ_tests/braket/simulator_v2/test_local_braket_simulator.py new file mode 100644 index 0000000..fc1ef48 --- /dev/null +++ b/test/integ_tests/braket/simulator_v2/test_local_braket_simulator.py @@ -0,0 +1,159 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT 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("braket_sv_v2") +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", + [ + ("braket_sv_v2", "StateVectorSimulatorV2"), + ("braket_dm_v2", "DensityMatrixSimulatorV2"), + ], +) +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/braket/simulator_v2/test_local_noise_simulator.py b/test/integ_tests/braket/simulator_v2/test_local_noise_simulator.py new file mode 100644 index 0000000..d96d6be --- /dev/null +++ b/test/integ_tests/braket/simulator_v2/test_local_noise_simulator.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. + +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_v2") +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/resources/grcs_16.json b/test/resources/grcs_16.json new file mode 100644 index 0000000..f6e2963 --- /dev/null +++ b/test/resources/grcs_16.json @@ -0,0 +1,125 @@ +{ + "ir": { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "h", "target": 1}, + {"type": "h", "target": 2}, + {"type": "h", "target": 3}, + {"type": "h", "target": 4}, + {"type": "h", "target": 5}, + {"type": "h", "target": 6}, + {"type": "h", "target": 7}, + {"type": "h", "target": 8}, + {"type": "h", "target": 9}, + {"type": "h", "target": 10}, + {"type": "h", "target": 11}, + {"type": "h", "target": 12}, + {"type": "h", "target": 13}, + {"type": "h", "target": 14}, + {"type": "h", "target": 15}, + {"type": "cz", "target": 1, "control": 0}, + {"type": "cz", "target": 7, "control": 6}, + {"type": "cz", "target": 9, "control": 8}, + {"type": "cz", "target": 15, "control": 14}, + {"type": "t", "target": 2}, + {"type": "t", "target": 3}, + {"type": "t", "target": 4}, + {"type": "t", "target": 5}, + {"type": "t", "target": 10}, + {"type": "t", "target": 11}, + {"type": "t", "target": 12}, + {"type": "t", "target": 13}, + {"type": "cz", "target": 8, "control": 4}, + {"type": "cz", "target": 10, "control": 6}, + {"type": "ry", "target": 0, "angle": 1.5707963267948966}, + {"type": "ry", "target": 1, "angle": 1.5707963267948966}, + {"type": "rx", "target": 7, "angle": 1.5707963267948966}, + {"type": "rx", "target": 9, "angle": 1.5707963267948966}, + {"type": "ry", "target": 14, "angle": 1.5707963267948966}, + {"type": "rx", "target": 15, "angle": 1.5707963267948966}, + {"type": "cz", "target": 2, "control": 1}, + {"type": "cz", "target": 10, "control": 9}, + {"type": "t", "target": 0}, + {"type": "ry", "target": 4, "angle": 1.5707963267948966}, + {"type": "rx", "target": 6, "angle": 1.5707963267948966}, + {"type": "t", "target": 7}, + {"type": "rx", "target": 8, "angle": 1.5707963267948966}, + {"type": "t", "target": 14}, + {"type": "t", "target": 15}, + {"type": "cz", "target": 4, "control": 0}, + {"type": "cz", "target": 13, "control": 9}, + {"type": "cz", "target": 6, "control": 2}, + {"type": "cz", "target": 15, "control": 11}, + {"type": "ry", "target": 1, "angle": 1.5707963267948966}, + {"type": "t", "target": 8}, + {"type": "ry", "target": 10, "angle": 1.5707963267948966}, + {"type": "cz", "target": 3, "control": 2}, + {"type": "cz", "target": 5, "control": 4}, + {"type": "cz", "target": 11, "control": 10}, + {"type": "cz", "target": 13, "control": 12}, + {"type": "ry", "target": 0, "angle": 1.5707963267948966}, + {"type": "t", "target": 1}, + {"type": "rx", "target": 6, "angle": 1.5707963267948966}, + {"type": "ry", "target": 9, "angle": 1.5707963267948966}, + {"type": "ry", "target": 15, "angle": 1.5707963267948966}, + {"type": "cz", "target": 9, "control": 5}, + {"type": "cz", "target": 11, "control": 7}, + {"type": "t", "target": 0}, + {"type": "rx", "target": 2, "angle": 1.5707963267948966}, + {"type": "ry", "target": 3, "angle": 1.5707963267948966}, + {"type": "ry", "target": 4, "angle": 1.5707963267948966}, + {"type": "t", "target": 6}, + {"type": "ry", "target": 10, "angle": 1.5707963267948966}, + {"type": "ry", "target": 12, "angle": 1.5707963267948966}, + {"type": "rx", "target": 13, "angle": 1.5707963267948966}, + {"type": "t", "target": 15}, + {"type": "cz", "target": 6, "control": 5}, + {"type": "cz", "target": 14, "control": 13}, + {"type": "t", "target": 2}, + {"type": "t", "target": 3}, + {"type": "t", "target": 4}, + {"type": "ry", "target": 7, "angle": 1.5707963267948966}, + {"type": "ry", "target": 9, "angle": 1.5707963267948966}, + {"type": "t", "target": 10}, + {"type": "ry", "target": 11, "angle": 1.5707963267948966}, + {"type": "t", "target": 12}, + {"type": "cz", "target": 12, "control": 8}, + {"type": "cz", "target": 5, "control": 1}, + {"type": "cz", "target": 14, "control": 10}, + {"type": "cz", "target": 7, "control": 3}, + {"type": "rx", "target": 6, "angle": 1.5707963267948966}, + {"type": "t", "target": 9}, + {"type": "t", "target": 11}, + {"type": "rx", "target": 13, "angle": 1.5707963267948966}, + {"type": "cz", "target": 1, "control": 0}, + {"type": "cz", "target": 7, "control": 6}, + {"type": "cz", "target": 9, "control": 8}, + {"type": "cz", "target": 15, "control": 14}, + {"type": "rx", "target": 3, "angle": 1.5707963267948966}, + {"type": "ry", "target": 5, "angle": 1.5707963267948966}, + {"type": "ry", "target": 10, "angle": 1.5707963267948966}, + {"type": "ry", "target": 12, "angle": 1.5707963267948966}, + {"type": "t", "target": 13}, + {"type": "h", "target": 0}, + {"type": "h", "target": 1}, + {"type": "h", "target": 2}, + {"type": "h", "target": 3}, + {"type": "h", "target": 4}, + {"type": "h", "target": 5}, + {"type": "h", "target": 6}, + {"type": "h", "target": 7}, + {"type": "h", "target": 8}, + {"type": "h", "target": 9}, + {"type": "h", "target": 10}, + {"type": "h", "target": 11}, + {"type": "h", "target": 12}, + {"type": "h", "target": 13}, + {"type": "h", "target": 14}, + {"type": "h", "target": 15} + ], + "results": [ + {"type": "statevector"} + ] + }, + "probability_zero": 0.0000062 +} \ No newline at end of file diff --git a/test/resources/grcs_16.qasm b/test/resources/grcs_16.qasm new file mode 100644 index 0000000..1c761bb --- /dev/null +++ b/test/resources/grcs_16.qasm @@ -0,0 +1,108 @@ +OPENQASM 3.0; + +qubit[16] q; + +h q; +cz q[0], q[1]; +cz q[6], q[7]; +cz q[8], q[9]; +cz q[14], q[15]; + +t q[2:5]; +t q[10:13]; + +cz q[4], q[8]; +cz q[6], q[10]; + +ry(π/2) q[{0, 1, 14}]; +rx(π/2) q[{7, 9, 15}]; + +cz q[1], q[2]; +cz q[9], q[10]; + +t q[0]; + +ry(π/2) q[4]; +rx(π/2) q[6]; + +t q[7]; + +rx(π/2) q[8]; + +t q[14:15]; + +cz q[0], q[4]; +cz q[9], q[13]; +cz q[2], q[6]; +cz q[11], q[15]; + +ry(π/2) q[1]; + +t q[8]; + +ry(π/2) q[10]; + +cz q[2], q[3]; +cz q[4], q[5]; +cz q[10], q[11]; +cz q[12], q[13]; + +ry(π/2) q[0]; + +t q[1]; + +rx(π/2) q[6]; +ry(π/2) q[{9, 15}]; + +cz q[5], q[9]; +cz q[7], q[11]; + +t q[0]; + +rx(π/2) q[2]; +ry(π/2) q[3:4]; + +t q[6]; + +ry(π/2) q[{10, 12}]; +rx(π/2) q[13]; + +t q[15]; + +cz q[5], q[6]; +cz q[13], q[14]; + +t q[2:4]; + +ry(π/2) q[{7, 9}]; + +t q[10]; + +ry(π/2) q[11]; + +t q[12]; + +cz q[8], q[12]; +cz q[1], q[5]; +cz q[10], q[14]; +cz q[3], q[7]; + +rx(π/2) q[6]; + +t q[{9, 11}]; + +rx(π/2) q[13]; + +cz q[0], q[1]; +cz q[6], q[7]; +cz q[8], q[9]; +cz q[14], q[15]; + +rx(π/2) q[3]; +ry(π/2) q[{5, 10, 12}]; + +t q[13]; + +h q; + +#pragma braket result state_vector \ No newline at end of file diff --git a/test/resources/grcs_8.json b/test/resources/grcs_8.json new file mode 100644 index 0000000..2524153 --- /dev/null +++ b/test/resources/grcs_8.json @@ -0,0 +1,64 @@ +{ + "ir": { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "h", "target": 1}, + {"type": "h", "target": 2}, + {"type": "h", "target": 3}, + {"type": "h", "target": 4}, + {"type": "h", "target": 5}, + {"type": "h", "target": 6}, + {"type": "h", "target": 7}, + {"type": "cz", "target": 1, "control": 0}, + {"type": "cz", "target": 7, "control": 6}, + {"type": "t", "target": 2}, + {"type": "t", "target": 3}, + {"type": "t", "target": 4}, + {"type": "t", "target": 5}, + {"type": "ry", "target": 0, "angle": 1.5707963267948966}, + {"type": "ry", "target": 1, "angle": 1.5707963267948966}, + {"type": "rx", "target": 7, "angle": 1.5707963267948966}, + {"type": "cz", "target": 2, "control": 1}, + {"type": "t", "target": 0}, + {"type": "ry", "target": 4, "angle": 1.5707963267948966}, + {"type": "rx", "target": 6, "angle": 1.5707963267948966}, + {"type": "t", "target": 7}, + {"type": "cz", "target": 4, "control": 0}, + {"type": "cz", "target": 6, "control": 2}, + {"type": "ry", "target": 1, "angle": 1.5707963267948966}, + {"type": "cz", "target": 3, "control": 2}, + {"type": "cz", "target": 5, "control": 4}, + {"type": "ry", "target": 0, "angle": 1.5707963267948966}, + {"type": "t", "target": 1}, + {"type": "rx", "target": 6, "angle": 1.5707963267948966}, + {"type": "t", "target": 0}, + {"type": "rx", "target": 2, "angle": 1.5707963267948966}, + {"type": "ry", "target": 3, "angle": 1.5707963267948966}, + {"type": "ry", "target": 4, "angle": 1.5707963267948966}, + {"type": "t", "target": 6}, + {"type": "cz", "target": 6, "control": 5}, + {"type": "t", "target": 2}, + {"type": "t", "target": 3}, + {"type": "t", "target": 4}, + {"type": "cz", "target": 5, "control": 1}, + {"type": "cz", "target": 7, "control": 3}, + {"type": "rx", "target": 6, "angle": 1.5707963267948966}, + {"type": "cz", "target": 1, "control": 0}, + {"type": "cz", "target": 7, "control": 6}, + {"type": "rx", "target": 3, "angle": 1.5707963267948966}, + {"type": "ry", "target": 5, "angle": 1.5707963267948966}, + {"type": "h", "target": 0}, + {"type": "h", "target": 1}, + {"type": "h", "target": 2}, + {"type": "h", "target": 3}, + {"type": "h", "target": 4}, + {"type": "h", "target": 5}, + {"type": "h", "target": 6}, + {"type": "h", "target": 7} + ], + "results": [ + {"type": "densitymatrix"} + ] + }, + "probability_zero": 0.0007324 +} diff --git a/test/resources/grcs_8.qasm b/test/resources/grcs_8.qasm new file mode 100644 index 0000000..d3cd667 --- /dev/null +++ b/test/resources/grcs_8.qasm @@ -0,0 +1,61 @@ +OPENQASM 3.0; + +qubit[8] q; + +h q; +cz q[0], q[1]; +cz q[6], q[7]; + +t q[2:5]; + +ry(π/2) q[0:1]; +rx(π/2) q[7]; + +cz q[1], q[2]; + +t q[0]; + +ry(π/2) q[4]; +rx(π/2) q[6]; + +t q[7]; + +cz q[0], q[4]; +cz q[2], q[6]; + +ry(π/2) q[1]; + +cz q[2], q[3]; +cz q[4], q[5]; + +ry(π/2) q[0]; + +t q[1]; + +rx(π/2) q[6]; + +t q[0]; + +rx(π/2) q[2]; +ry(π/2) q[3:4]; + +t q[6]; + +cz q[5], q[6]; + +t q[2:4]; + +cz q[1], q[5]; +cz q[3], q[7]; + +rx(π/2) q[6]; + +cz q[0], q[1]; +cz q[6], q[7]; + +rx(π/2) q[3]; +ry(π/2) q[5]; + +h q; + +#pragma braket result density_matrix diff --git a/test/unit_tests/braket/simulator_v2/test_density_matrix_simulator.py b/test/unit_tests/braket/simulator_v2/test_density_matrix_simulator.py new file mode 100644 index 0000000..82135ce --- /dev/null +++ b/test/unit_tests/braket/simulator_v2/test_density_matrix_simulator.py @@ -0,0 +1,848 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT 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 cmath +import json +import sys +from collections import Counter, namedtuple + +import numpy as np +import pytest + +from braket.device_schema.simulators import ( + GateModelSimulatorDeviceCapabilities, + GateModelSimulatorDeviceParameters, +) +from braket.ir.jaqcd import Expectation +from braket.ir.jaqcd import Program as JaqcdProgram +from braket.ir.openqasm import Program as OpenQASMProgram +from braket.simulator_v2 import DensityMatrixSimulatorV2 as DensityMatrixSimulator +from braket.task_result import AdditionalMetadata, TaskMetadata + +CircuitData = namedtuple("CircuitData", "circuit_ir probability_zero") + + +@pytest.fixture(params=["OpenQASM", "Jaqcd"]) +def ir_type(request): + return request.param + + +@pytest.fixture +def noisy_circuit_2_qubit(): + return ( + JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "x", "target": 0}, + {"type": "x", "target": 1}, + {"type": "bit_flip", "target": 1, "probability": 0.1}, + ] + } + ) + ) + if ir_type == "Jaqcd" + else OpenQASMProgram( + source=""" + OPENQASM 3.0; + qubit[2] q; + + x q; + #pragma braket noise bit_flip(.1) q[1] + """ + ) + ) + + +@pytest.fixture +def grcs_8_qubit(ir_type): + if ir_type == "Jaqcd": + with open("test/resources/grcs_8.json") as circuit_file: + data = json.load(circuit_file) + return CircuitData( + JaqcdProgram.parse_raw(json.dumps(data["ir"])), + data["probability_zero"], + ) + return CircuitData(OpenQASMProgram(source="test/resources/grcs_8.qasm"), 0.0007324) + + +@pytest.fixture +def bell_ir(ir_type): + return ( + JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + ] + } + ) + ) + if ir_type == "Jaqcd" + else OpenQASMProgram( + source=""" + OPENQASM 3.0; + qubit[2] q; + + h q[0]; + cnot q[0], q[1]; + """ + ) + ) + + +def test_simulator_run_noisy_circuit(noisy_circuit_2_qubit, caplog): + simulator = DensityMatrixSimulator() + shots_count = 10000 + if isinstance(noisy_circuit_2_qubit, JaqcdProgram): + result = simulator.run(noisy_circuit_2_qubit, qubit_count=2, shots=shots_count) + else: + result = simulator.run(noisy_circuit_2_qubit, shots=shots_count) + + assert all([len(measurement) == 2] for measurement in result.measurements) + assert len(result.measurements) == shots_count + counter = Counter( + ["".join([str(m) for m in measurement]) for measurement in result.measurements] + ) + assert counter.keys() == {"10", "11"} + assert 0.0 < counter["10"] / (counter["10"] + counter["11"]) < 0.2 + assert 0.8 < counter["11"] / (counter["10"] + counter["11"]) < 1.0 + assert result.taskMetadata == TaskMetadata( + id=result.taskMetadata.id, + deviceId=DensityMatrixSimulator.DEVICE_ID, + shots=shots_count, + ) + assert result.additionalMetadata == AdditionalMetadata(action=noisy_circuit_2_qubit) + assert not caplog.text + + +def test_simulator_run_bell_pair(bell_ir, caplog): + simulator = DensityMatrixSimulator() + shots_count = 10000 + if isinstance(bell_ir, JaqcdProgram): + result = simulator.run(bell_ir, qubit_count=2, shots=shots_count) + else: + result = simulator.run(bell_ir, shots=shots_count) + + assert all([len(measurement) == 2] for measurement in result.measurements) + assert len(result.measurements) == shots_count + counter = Counter( + ["".join([str(m) for m in measurement]) for measurement in result.measurements] + ) + assert counter.keys() == {"00", "11"} + assert 0.4 < counter["00"] / (counter["00"] + counter["11"]) < 0.6 + assert 0.4 < counter["11"] / (counter["00"] + counter["11"]) < 0.6 + assert result.taskMetadata == TaskMetadata( + id=result.taskMetadata.id, + deviceId=DensityMatrixSimulator.DEVICE_ID, + shots=shots_count, + ) + assert result.additionalMetadata == AdditionalMetadata(action=bell_ir) + assert not caplog.text + + +@pytest.mark.xfail(raises=ValueError) +def test_simulator_run_no_results_no_shots(bell_ir): + simulator = DensityMatrixSimulator() + if isinstance(bell_ir, JaqcdProgram): + simulator.run(bell_ir, qubit_count=2, shots=0) + else: + simulator.run(bell_ir, shots=0) + + +def test_simulator_run_grcs_8(grcs_8_qubit): + simulator = DensityMatrixSimulator() + if isinstance(grcs_8_qubit.circuit_ir, JaqcdProgram): + result = simulator.run(grcs_8_qubit.circuit_ir, qubit_count=8, shots=0) + else: + result = simulator.run(grcs_8_qubit.circuit_ir, shots=0) + density_matrix = result.resultTypes[0].value + assert cmath.isclose( + density_matrix[0][0].real, grcs_8_qubit.probability_zero, abs_tol=1e-7 + ) + + +def test_properties(): + simulator = DensityMatrixSimulator() + observables = ["x", "y", "z", "h", "i", "hermitian"] + max_shots = sys.maxsize + qubit_count = 16 + expected_properties = GateModelSimulatorDeviceCapabilities.parse_obj( + { + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "00:00", + "windowEndHour": "23:59:59", + } + ], + "shotsRange": [0, max_shots], + }, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": sorted( + [ + # OpenQASM primitives + "U", + "GPhase", + # builtin Braket gates + "ccnot", + "cnot", + "cphaseshift", + "cphaseshift00", + "cphaseshift01", + "cphaseshift10", + "cswap", + "cv", + "cy", + "cz", + "ecr", + "gpi", + "gpi2", + "h", + "i", + "iswap", + "ms", + "pswap", + "phaseshift", + "rx", + "ry", + "rz", + "s", + "si", + "swap", + "t", + "ti", + "unitary", + "v", + "vi", + "x", + "xx", + "xy", + "y", + "yy", + "z", + "zz", + # noise operations + "bit_flip", + "phase_flip", + "pauli_channel", + "depolarizing", + "two_qubit_depolarizing", + "two_qubit_dephasing", + "amplitude_damping", + "generalized_amplitude_damping", + "phase_damping", + "kraus", + ] + ), + "supportedModifiers": [ + { + "name": "ctrl", + }, + { + "name": "negctrl", + }, + { + "name": "pow", + "exponent_types": ["int", "float"], + }, + { + "name": "inv", + }, + ], + "supportedPragmas": [ + "braket_unitary_matrix", + "braket_result_type_state_vector", + "braket_result_type_density_matrix", + "braket_result_type_sample", + "braket_result_type_expectation", + "braket_result_type_variance", + "braket_result_type_probability", + "braket_result_type_amplitude", + "braket_noise_amplitude_damping", + "braket_noise_bit_flip", + "braket_noise_depolarizing", + "braket_noise_kraus", + "braket_noise_pauli_channel", + "braket_noise_generalized_amplitude_damping", + "braket_noise_phase_flip", + "braket_noise_phase_damping", + "braket_noise_two_qubit_dephasing", + "braket_noise_two_qubit_depolarizing", + ], + "forbiddenPragmas": [ + "braket_result_type_adjoint_gradient", + ], + "supportedResultTypes": [ + { + "name": "Sample", + "observables": observables, + "minShots": 1, + "maxShots": max_shots, + }, + { + "name": "Expectation", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Variance", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Probability", + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "DensityMatrix", + "minShots": 0, + "maxShots": 0, + }, + ], + "supportPhysicalQubits": False, + "supportsPartialVerbatimBox": False, + "requiresContiguousQubitIndices": True, + "requiresAllQubitsMeasurement": True, + "supportsUnassignedMeasurements": True, + "disabledQubitRewiringSupported": False, + }, + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": [ + "amplitude_damping", + "bit_flip", + "ccnot", + "cnot", + "cphaseshift", + "cphaseshift00", + "cphaseshift01", + "cphaseshift10", + "cswap", + "cv", + "cy", + "cz", + "depolarizing", + "ecr", + "generalized_amplitude_damping", + "h", + "i", + "iswap", + "kraus", + "pauli_channel", + "two_qubit_pauli_channel", + "phase_flip", + "phase_damping", + "phaseshift", + "pswap", + "rx", + "ry", + "rz", + "s", + "si", + "swap", + "t", + "ti", + "two_qubit_dephasing", + "two_qubit_depolarizing", + "unitary", + "v", + "vi", + "x", + "xx", + "xy", + "y", + "yy", + "z", + "zz", + ], + "supportedResultTypes": [ + { + "name": "Sample", + "observables": observables, + "minShots": 1, + "maxShots": max_shots, + }, + { + "name": "Expectation", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Variance", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Probability", + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "DensityMatrix", + "minShots": 0, + "maxShots": 0, + }, + ], + }, + }, + "paradigm": {"qubitCount": qubit_count}, + "deviceParameters": GateModelSimulatorDeviceParameters.schema(), + } + ) + assert simulator.properties == expected_properties + + +def test_openqasm_density_matrix_simulator(): + noisy_bell_qasm = """ + qubit[2] qs; + + h qs[0]; + cnot qs[0], qs[1]; + + #pragma braket noise bit_flip(.2) qs[1] + + #pragma braket result probability + """ + device = DensityMatrixSimulator() + program = OpenQASMProgram(source=noisy_bell_qasm) + result = device.run(program) + probabilities = result.resultTypes[0].value + assert np.allclose(probabilities, [0.4, 0.1, 0.1, 0.4]) + + +invalid_ir_result_types = [ + {"type": "statevector"}, + {"type": "amplitude", "states": ["11"]}, +] + + +@pytest.fixture +def bell_ir_with_result(ir_type): + def _bell_ir_with_result(targets=None): + if ir_type == "Jaqcd": + return JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + ], + "results": [ + { + "type": "expectation", + "observable": ["x"], + "targets": targets, + }, + ], + } + ) + ) + if targets is None: + observable_string = "x all" + elif len(targets) == 1: + observable_string = f"x(q[{targets[0]}])" + else: + raise ValueError("bad test") + + return OpenQASMProgram( + source=f""" + qubit[2] q; + + h q[0]; + cnot q[0], q[1]; + + #pragma braket result expectation {observable_string} + """ + ) + + return _bell_ir_with_result + + +@pytest.mark.parametrize("result_type", invalid_ir_result_types) +@pytest.mark.xfail(raises=TypeError) +def test_simulator_run_invalid_ir_result_types(result_type): + simulator = DensityMatrixSimulator() + ir = JaqcdProgram.parse_raw( + json.dumps( + {"instructions": [{"type": "h", "target": 0}], "results": [result_type]} + ) + ) + simulator.run(ir, qubit_count=2, shots=100) + + +@pytest.mark.parametrize( + "result_type", + ( + "#pragma braket result state_vector", + "#pragma braket result density_matrix", + '#pragma braket result amplitude "0"', + ), +) +def test_simulator_run_invalid_ir_result_types_openqasm(result_type): + simulator = DensityMatrixSimulator() + ir = OpenQASMProgram( + source=f""" + qubit q; + h q; + {result_type} + """ + ) + with pytest.raises(TypeError): + simulator.run(ir, qubit_count=2, shots=100) + + +def test_simulator_run_densitymatrix_shots(): + simulator = DensityMatrixSimulator() + jaqcd = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [{"type": "h", "target": 0}], + "results": [{"type": "densitymatrix"}], + } + ) + ) + qasm = OpenQASMProgram( + source=""" + qubit q; + h q; + #pragma braket result density_matrix + """ + ) + with pytest.raises(ValueError): + simulator.run(jaqcd, qubit_count=2, shots=100) + with pytest.raises(ValueError): + simulator.run(qasm, shots=100) + + +def test_simulator_run_result_types_shots(caplog): + simulator = DensityMatrixSimulator() + jaqcd = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + ], + "results": [ + { + "type": "expectation", + "observable": ["z"], + "targets": [1], + } + ], + } + ) + ) + qasm = OpenQASMProgram( + source=""" + qubit[2] q; + h q[0]; + cnot q[0], q[1]; + #pragma braket result expectation z(q[1]) + """ + ) + shots_count = 100 + jaqcd_result = simulator.run(jaqcd, qubit_count=2, shots=shots_count) + qasm_result = simulator.run(qasm, shots=shots_count) + for result in jaqcd_result, qasm_result: + assert all([len(measurement) == 2] for measurement in result.measurements) + assert len(result.measurements) == shots_count + assert result.measuredQubits == [0, 1] + assert not jaqcd_result.resultTypes + assert not caplog.text + + +def test_simulator_run_result_types_shots_basis_rotation_gates(caplog): + simulator = DensityMatrixSimulator() + jaqcd = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + ], + "basis_rotation_instructions": [{"type": "h", "target": 1}], + "results": [ + { + "type": "expectation", + "observable": ["x"], + "targets": [1], + } + ], + } + ) + ) + qasm = OpenQASMProgram( + source=""" + qubit[2] q; + h q[0]; + cnot q[0], q[1]; + #pragma braket result expectation x(q[1]) + """ + ) + shots_count = 1000 + jaqcd_result = simulator.run(jaqcd, qubit_count=2, shots=shots_count) + qasm_result = simulator.run(qasm, shots=shots_count) + for result in jaqcd_result, qasm_result: + assert all([len(measurement) == 2] for measurement in result.measurements) + assert len(result.measurements) == shots_count + assert result.measuredQubits == [0, 1] + assert not jaqcd_result.resultTypes + assert not caplog.text + + +@pytest.mark.xfail(raises=ValueError) +def test_simulator_run_result_types_shots_basis_rotation_gates_value_error(): + simulator = DensityMatrixSimulator() + ir = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + ], + "basis_rotation_instructions": [{"type": "foo", "target": 1}], + "results": [ + {"type": "expectation", "observable": ["x"], "targets": [1]} + ], + } + ) + ) + shots_count = 1000 + simulator.run(ir, qubit_count=2, shots=shots_count) + + +@pytest.mark.parametrize("targets", [(None), ([1]), ([0])]) +def test_simulator_bell_pair_result_types(bell_ir_with_result, targets, caplog): + simulator = DensityMatrixSimulator() + ir = bell_ir_with_result(targets) + if isinstance(ir, JaqcdProgram): + result = simulator.run(ir, qubit_count=2, shots=0) + else: + result = simulator.run(ir, shots=0) + assert len(result.resultTypes) == 1 + expected_expectation = Expectation(observable=["x"], targets=targets) + assert result.resultTypes[0].type == expected_expectation + assert np.allclose(result.resultTypes[0].value, 0 if targets else [0, 0]) + assert result.taskMetadata == TaskMetadata( + id=result.taskMetadata.id, + deviceId=DensityMatrixSimulator.DEVICE_ID, + shots=0, + ) + assert result.additionalMetadata == AdditionalMetadata(action=ir) + assert not caplog.text + + +def test_simulator_fails_samples_0_shots(): + simulator = DensityMatrixSimulator() + jaqcd = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [{"type": "h", "target": 0}], + "results": [{"type": "sample", "observable": ["x"], "targets": [0]}], + } + ) + ) + qasm = OpenQASMProgram( + source=""" + qubit q; + h q; + #pragma braket result sample x(q) + """ + ) + with pytest.raises(ValueError): + simulator.run(jaqcd, qubit_count=1, shots=0) + with pytest.raises(ValueError): + simulator.run(qasm, shots=0) + + +@pytest.mark.parametrize( + "result_types,expected", + [ + ( + [ + {"type": "expectation", "observable": ["x"], "targets": [1]}, + {"type": "variance", "observable": ["x"], "targets": [1]}, + ], + [0, 1], + ), + ( + [ + {"type": "expectation", "observable": ["x"]}, + {"type": "variance", "observable": ["x"], "targets": [1]}, + ], + [[0, 0], 1], + ), + ( + [ + { + "type": "expectation", + "observable": [[[[0, 0], [1, 0]], [[1, 0], [0, 0]]]], + "targets": [1], + }, + { + "type": "variance", + "observable": [[[[0, 0], [1, 0]], [[1, 0], [0, 0]]]], + "targets": [1], + }, + ], + [0, 1], + ), + ( + [ + { + "type": "expectation", + "observable": ["x", [[[0, 0], [1, 0]], [[1, 0], [0, 0]]]], + "targets": [0, 1], + }, + { + "type": "expectation", + "observable": ["x", [[[0, 0], [1, 0]], [[1, 0], [0, 0]]]], + "targets": [0, 1], + }, + ], + [1, 1], + ), + ( + [ + {"type": "variance", "observable": ["x"], "targets": [1]}, + {"type": "expectation", "observable": ["x"]}, + { + "type": "expectation", + "observable": ["x", [[[0, 0], [1, 0]], [[1, 0], [0, 0]]]], + "targets": [0, 1], + }, + ], + [1, [0, 0], 1], + ), + ], +) +def test_simulator_valid_observables(result_types, expected): + simulator = DensityMatrixSimulator() + prog = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + ], + "results": result_types, + } + ) + ) + result = simulator.run(prog, qubit_count=2, shots=0) + for i in range(len(result_types)): + assert np.allclose(result.resultTypes[i].value, expected[i]) + + +@pytest.mark.parametrize( + "result_types,expected", + [ + ( + """ + #pragma braket result expectation x(q[1]) + #pragma braket result variance x(q[1]) + """, + [0, 1], + ), + ( + """ + #pragma braket result expectation x all + #pragma braket result variance x(q[1]) + """, + [[0, 0], 1], + ), + ( + """ + #pragma braket result expectation hermitian([[0, 1], [1, 0]]) q[1] + #pragma braket result variance hermitian([[0, 1], [1, 0]]) q[1] + """, + [0, 1], + ), + ( + """ + #pragma braket result expectation x(q[0]) @ hermitian([[0, 1], [1, 0]]) q[1] + #pragma braket result expectation x(q[0]) @ hermitian([[0, 1], [1, 0]]) q[1] + """, + [1, 1], + ), + ( + """ + #pragma braket result variance x(q[1]) + #pragma braket result expectation x all + #pragma braket result expectation x(q[0]) @ hermitian([[0, 1], [1, 0]]) q[1] + """, + [1, [0, 0], 1], + ), + ], +) +def test_simulator_valid_observables_qasm(result_types, expected, caplog): + simulator = DensityMatrixSimulator() + prog = OpenQASMProgram( + source=f""" + qubit[2] q; + h q[0]; + cnot q[0], q[1]; + {result_types} + """ + ) + result = simulator.run(prog, shots=0) + for i in range(len(result_types.split("\n")) - 2): + assert np.allclose(result.resultTypes[i].value, expected[i]) + assert not caplog.text + + +def test_adjoint_gradient_pragma_dm1(): + simulator = DensityMatrixSimulator() + prog = OpenQASMProgram( + source=""" + input float alpha; + input float beta; + qubit[1] q; + h q[0]; + #pragma braket result adjoint_gradient h(q[0]) alpha, beta + """, + inputs={"alpha": 0.2, "beta": 0.3}, + ) + ag_not_supported = "Result type adjoint_gradient is not supported." + + with pytest.raises(TypeError, match=ag_not_supported): + simulator.run(prog, shots=0) + + +def test_measure_targets(): + qasm = """ + qubit[2] q; + bit[1] b; + h q[0]; + cnot q[0], q[1]; + b[0] = measure q[0]; + """ + simulator = DensityMatrixSimulator() + result = simulator.run(OpenQASMProgram(source=qasm), shots=1000) + measurements = np.array(result.measurements, dtype=int) + assert 400 < np.sum(measurements, axis=0)[0] < 600 + assert len(measurements[0]) == 1 + assert result.measuredQubits == [0] diff --git a/test/unit_tests/braket/simulator_v2/test_state_vector_simulator.py b/test/unit_tests/braket/simulator_v2/test_state_vector_simulator.py new file mode 100644 index 0000000..d3e2282 --- /dev/null +++ b/test/unit_tests/braket/simulator_v2/test_state_vector_simulator.py @@ -0,0 +1,1412 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT 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 cmath +import json +import re + +# import re +import sys +from collections import Counter, namedtuple + +import numpy as np +import pytest + +from braket.default_simulator import observables +from braket.device_schema.simulators import ( + GateModelSimulatorDeviceCapabilities, + GateModelSimulatorDeviceParameters, +) +from braket.ir.jaqcd import Amplitude, DensityMatrix, Expectation, Probability +from braket.ir.jaqcd import Program as JaqcdProgram +from braket.ir.jaqcd import StateVector, Variance +from braket.ir.openqasm import Program as OpenQASMProgram +from braket.simulator_v2 import StateVectorSimulatorV2 as StateVectorSimulator +from braket.task_result import AdditionalMetadata, TaskMetadata + +CircuitData = namedtuple("CircuitData", "circuit_ir probability_zero") + + +@pytest.fixture(params=["OpenQASM", "Jaqcd"]) +def ir_type(request): + return request.param + + +@pytest.fixture +def grcs_16_qubit(ir_type): + if ir_type == "Jaqcd": + with open("test/resources/grcs_16.json") as circuit_file: + data = json.load(circuit_file) + return CircuitData( + JaqcdProgram.parse_raw(json.dumps(data["ir"])), data["probability_zero"] + ) + return CircuitData(OpenQASMProgram(source="test/resources/grcs_16.qasm"), 0.0000062) + + +@pytest.fixture +def bell_ir(ir_type): + return ( + JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + ] + } + ) + ) + if ir_type == "Jaqcd" + else OpenQASMProgram( + source=""" + OPENQASM 3.0; + qubit[2] q; + + h q[0]; + cnot q[0], q[1]; + """ + ) + ) + + +@pytest.mark.parametrize("batch_size", [1]) +def test_simulator_run_grcs_16(grcs_16_qubit, batch_size): + simulator = StateVectorSimulator() + if isinstance(grcs_16_qubit.circuit_ir, JaqcdProgram): + result = simulator.run( + grcs_16_qubit.circuit_ir, + qubit_count=16, + shots=0, + batch_size=batch_size, + ) + else: + result = simulator.run(grcs_16_qubit.circuit_ir, shots=0, batch_size=batch_size) + state_vector = result.resultTypes[0].value + assert cmath.isclose( + abs(state_vector[0]) ** 2, grcs_16_qubit.probability_zero, abs_tol=1e-7 + ) + + +@pytest.mark.parametrize("batch_size", [1]) +def test_simulator_run_bell_pair(bell_ir, batch_size, caplog): + simulator = StateVectorSimulator() + shots_count = 10000 + if isinstance(bell_ir, JaqcdProgram): + result = simulator.run( + bell_ir, qubit_count=2, shots=shots_count, batch_size=batch_size + ) + else: + result = simulator.run(bell_ir, shots=shots_count, batch_size=batch_size) + + assert all([len(measurement) == 2] for measurement in result.measurements) + assert len(result.measurements) == shots_count + counter = Counter( + ["".join([str(m) for m in measurement]) for measurement in result.measurements] + ) + assert counter.keys() == {"00", "11"} + assert 0.4 < counter["00"] / (counter["00"] + counter["11"]) < 0.6 + assert 0.4 < counter["11"] / (counter["00"] + counter["11"]) < 0.6 + assert result.taskMetadata == TaskMetadata( + id=result.taskMetadata.id, + deviceId=StateVectorSimulator.DEVICE_ID, + shots=shots_count, + ) + assert result.additionalMetadata == AdditionalMetadata(action=bell_ir) + assert not caplog.text + + +def test_properties(): + simulator = StateVectorSimulator() + observables = ["x", "y", "z", "h", "i", "hermitian"] + max_shots = sys.maxsize + qubit_count = 32 + expected_properties = GateModelSimulatorDeviceCapabilities.parse_obj( + { + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "00:00", + "windowEndHour": "23:59:59", + } + ], + "shotsRange": [0, max_shots], + }, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": [ + # OpenQASM primitives + "U", + "GPhase", + # builtin Braket gates + "ccnot", + "cnot", + "cphaseshift", + "cphaseshift00", + "cphaseshift01", + "cphaseshift10", + "cswap", + "cv", + "cy", + "cz", + "ecr", + "gpi", + "gpi2", + "h", + "i", + "iswap", + "ms", + "pswap", + "phaseshift", + "rx", + "ry", + "rz", + "s", + "si", + "swap", + "t", + "ti", + "unitary", + "v", + "vi", + "x", + "xx", + "xy", + "y", + "yy", + "z", + "zz", + ], + "supportedModifiers": [ + { + "name": "ctrl", + }, + { + "name": "negctrl", + }, + { + "name": "pow", + "exponent_types": ["int", "float"], + }, + { + "name": "inv", + }, + ], + "supportedPragmas": [ + "braket_unitary_matrix", + "braket_result_type_state_vector", + "braket_result_type_density_matrix", + "braket_result_type_sample", + "braket_result_type_expectation", + "braket_result_type_variance", + "braket_result_type_probability", + "braket_result_type_amplitude", + ], + "forbiddenPragmas": [ + "braket_noise_amplitude_damping", + "braket_noise_bit_flip", + "braket_noise_depolarizing", + "braket_noise_kraus", + "braket_noise_pauli_channel", + "braket_noise_generalized_amplitude_damping", + "braket_noise_phase_flip", + "braket_noise_phase_damping", + "braket_noise_two_qubit_dephasing", + "braket_noise_two_qubit_depolarizing", + "braket_result_type_adjoint_gradient", + ], + "supportedResultTypes": [ + { + "name": "Sample", + "observables": observables, + "minShots": 1, + "maxShots": max_shots, + }, + { + "name": "Expectation", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Variance", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Probability", + "minShots": 0, + "maxShots": max_shots, + }, + {"name": "StateVector", "minShots": 0, "maxShots": 0}, + { + "name": "DensityMatrix", + "minShots": 0, + "maxShots": 0, + }, + {"name": "Amplitude", "minShots": 0, "maxShots": 0}, + ], + "supportPhysicalQubits": False, + "supportsPartialVerbatimBox": False, + "requiresContiguousQubitIndices": True, + "requiresAllQubitsMeasurement": True, + "supportsUnassignedMeasurements": True, + "disabledQubitRewiringSupported": False, + }, + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], + "supportedOperations": [ + "ccnot", + "cnot", + "cphaseshift", + "cphaseshift00", + "cphaseshift01", + "cphaseshift10", + "cswap", + "cv", + "cy", + "cz", + "ecr", + "h", + "i", + "iswap", + "pswap", + "phaseshift", + "rx", + "ry", + "rz", + "s", + "si", + "swap", + "t", + "ti", + "unitary", + "v", + "vi", + "x", + "xx", + "xy", + "y", + "yy", + "z", + "zz", + ], + "supportedResultTypes": [ + { + "name": "Sample", + "observables": observables, + "minShots": 1, + "maxShots": max_shots, + }, + { + "name": "Expectation", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Variance", + "observables": observables, + "minShots": 0, + "maxShots": max_shots, + }, + { + "name": "Probability", + "minShots": 0, + "maxShots": max_shots, + }, + {"name": "StateVector", "minShots": 0, "maxShots": 0}, + { + "name": "DensityMatrix", + "minShots": 0, + "maxShots": 0, + }, + {"name": "Amplitude", "minShots": 0, "maxShots": 0}, + ], + }, + }, + "paradigm": {"qubitCount": qubit_count}, + "deviceParameters": GateModelSimulatorDeviceParameters.schema(), + } + ) + assert simulator.properties == expected_properties + + +# def test_alias(): +# assert StateVectorSimulator().properties == DefaultSimulator().properties + + +@pytest.fixture +def sv_adder(): + return """ + OPENQASM 3; + + input uint[4] a_in; + input uint[4] b_in; + + gate majority a, b, c { + cnot c, b; + cnot c, a; + ccnot a, b, c; + } + + gate unmaj a, b, c { + ccnot a, b, c; + cnot c, a; + cnot a, b; + } + + qubit cin; + qubit[4] a; + qubit[4] b; + qubit cout; + + // set input states + for int[8] i in [0: 3] { + if(bool(a_in[i])) x a[i]; + if(bool(b_in[i])) x b[i]; + } + + // add a to b, storing result in b + majority cin, b[3], a[3]; + for int[8] i in [3: -1: 1] { majority a[i], b[i - 1], a[i - 1]; } + cnot a[0], cout; + for int[8] i in [1: 3] { unmaj a[i], b[i - 1], a[i - 1]; } + unmaj cin, b[3], a[3]; + + // todo: subtle bug when trying to get a result type for both at once + #pragma braket result probability cout, b + #pragma braket result probability cout + #pragma braket result probability b + """ + + +def test_gphase(): + qasm = """ + qubit[2] qs; + + int[8] two = 2; + + gate x a { U(π, 0, π) a; } + gate cx c, a { ctrl @ x c, a; } + gate phase c, a { + gphase(π/2); + pow(1) @ ctrl(two) @ gphase(π) c, a; + } + gate h a { U(π/2, 0, π) a; } + + inv @ U(π/2, 0, π) qs[0]; + cx qs[0], qs[1]; + phase qs[0], qs[1]; + + gphase(π); + inv @ gphase(π / 2); + negctrl @ ctrl @ gphase(2 * π) qs[0], qs[1]; + + #pragma braket result amplitude '00', '01', '10', '11' + """ + simulator = StateVectorSimulator() + result = simulator.run(OpenQASMProgram(source=qasm), shots=0) + sv = [result.resultTypes[0].value[state] for state in ("00", "01", "10", "11")] + assert np.allclose(sv, [-1 / np.sqrt(2), 0, 0, 1 / np.sqrt(2)]) + + +def test_adder(sv_adder): + simulator = StateVectorSimulator() + inputs = {"a_in": 7, "b_in": 3} + result = simulator.run(OpenQASMProgram(source=sv_adder, inputs=inputs), shots=100) + assert result.resultTypes[0] == Probability(targets=[9, 5, 6, 7, 8]) + + +def test_adder_analytic(sv_adder): + simulator = StateVectorSimulator() + inputs = {"a_in": 7, "b_in": 3} + result = simulator.run(OpenQASMProgram(source=sv_adder, inputs=inputs)) + expected_probs = np.zeros(2**5) + expected_probs[10] = 1 + probs = np.outer(result.resultTypes[1].value, result.resultTypes[2].value).flatten() + assert np.allclose(probs, expected_probs) + + +def test_result_types_analytic(): + simulator = StateVectorSimulator() + qasm = """ + qubit[3] q; + bit[3] c; + + h q[0]; + cnot q[0], q[1]; + cnot q[1], q[2]; + x q[2]; + + // {{ 001: .5, 110: .5 }} + + #pragma braket result state_vector + #pragma braket result probability + #pragma braket result probability all + #pragma braket result probability q + #pragma braket result probability q[0] + #pragma braket result probability q[0:1] + #pragma braket result probability q[{0, 2, 1}] + #pragma braket result amplitude "001", "110" + #pragma braket result density_matrix + #pragma braket result density_matrix q + #pragma braket result density_matrix q[0] + #pragma braket result density_matrix q[0:1] + #pragma braket result density_matrix q[0], q[1] + #pragma braket result density_matrix q[{0, 2, 1}] + #pragma braket result expectation z(q[0]) + #pragma braket result expectation x all + #pragma braket result variance x(q[0]) @ z(q[2]) @ h(q[1]) + #pragma braket result expectation hermitian([[0, -1im], [0 + 1im, 0]]) q[0] + """ + program = OpenQASMProgram(source=qasm) + result = simulator.run(program, shots=0) + + result_types = result.resultTypes + + assert result_types[0].type == StateVector() + assert result_types[1].type == Probability() + assert result_types[2].type == Probability() + assert result_types[3].type == Probability(targets=(0, 1, 2)) + assert result_types[4].type == Probability(targets=(0,)) + assert result_types[5].type == Probability(targets=(0, 1)) + assert result_types[6].type == Probability(targets=(0, 2, 1)) + assert result_types[7].type == Amplitude(states=("001", "110")) + assert result_types[8].type == DensityMatrix() + assert result_types[9].type == DensityMatrix(targets=(0, 1, 2)) + assert result_types[10].type == DensityMatrix(targets=(0,)) + assert result_types[11].type == DensityMatrix(targets=(0, 1)) + assert result_types[12].type == DensityMatrix(targets=(0, 1)) + assert result_types[13].type == DensityMatrix(targets=(0, 2, 1)) + assert result_types[14].type == Expectation(observable=("z",), targets=(0,)) + assert result_types[15].type == Expectation(observable=("x",)) + assert result_types[16].type == Variance( + observable=("x", "z", "h"), targets=(0, 2, 1) + ) + assert result_types[17].type == Expectation( + observable=([[[0, 0], [0, -1]], [[0, 1], [0, 0]]],), + targets=(0,), + ) + + assert np.allclose( + result_types[0].value, + [0, 1 / np.sqrt(2), 0, 0, 0, 0, 1 / np.sqrt(2), 0], + ) + assert np.allclose( + result_types[1].value, + [0, 0.5, 0, 0, 0, 0, 0.5, 0], + ) + assert np.allclose( + result_types[2].value, + [0, 0.5, 0, 0, 0, 0, 0.5, 0], + ) + assert np.allclose( + result_types[3].value, + [0, 0.5, 0, 0, 0, 0, 0.5, 0], + ) + assert np.allclose( + result_types[4].value, + [0.5, 0.5], + ) + assert np.allclose( + result_types[5].value, + [0.5, 0, 0, 0.5], + ) + assert np.allclose( + result_types[6].value, + [0, 0, 0.5, 0, 0, 0.5, 0, 0], + ) + assert np.isclose(result_types[7].value["001"], 1 / np.sqrt(2)) + assert np.isclose(result_types[7].value["110"], 1 / np.sqrt(2)) + assert np.allclose( + result_types[8].value, + [ + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0.5, 0, 0, 0, 0, 0.5, 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], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0.5, 0, 0, 0, 0, 0.5, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + ], + ) + assert np.allclose( + result_types[9].value, + [ + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0.5, 0, 0, 0, 0, 0.5, 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], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0.5, 0, 0, 0, 0, 0.5, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + ], + ) + assert np.allclose( + result_types[10].value, + np.eye(2) * 0.5, + ) + assert np.allclose( + result_types[11].value, + [[0.5, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0.5]], + ) + assert np.allclose( + result_types[12].value, + [[0.5, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0.5]], + ) + assert np.allclose( + result_types[13].value, + [ + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0.5, 0, 0, 0.5, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0.5, 0, 0, 0.5, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + ], + ) + assert np.allclose(result_types[14].value, 0) + assert np.allclose(result_types[14].value, 0) + assert np.allclose(result_types[16].value, 1) + assert np.allclose(result_types[17].value, 0) + + +def test_invalid_standard_observable_target(): + qasm = """ + qubit[2] qs; + #pragma braket result variance x(qs) + """ + simulator = StateVectorSimulator() + program = OpenQASMProgram(source=qasm) + + must_be_one_qubit = "Standard observable target must be exactly 1 qubit." + + with pytest.raises(ValueError, match=must_be_one_qubit): + simulator.run(program, shots=0) + + +@pytest.mark.parametrize("shots", (0, 10)) +def test_invalid_hermitian_target(shots): + qasm = """ + OPENQASM 3.0; + qubit[3] q; + i q; + pragma braket result expectation hermitian([[-6+0im, 2+1im, -3+0im, -5+2im], [2-1im, 0im, 2-1im, -5+4im], [-3+0im, 2+1im, 0im, -4+3im], [-5-2im, -5-4im, -4-3im, -6+0im]]) q[0] # noqa: E501 + """ + simulator = StateVectorSimulator() + program = OpenQASMProgram(source=qasm) + + invalid_observable = re.escape( + "Invalid observable specified: [" + "[[-6.0, 0.0], [2.0, 1.0], [-3.0, 0.0], [-5.0, 2.0]], " + "[[2.0, -1.0], [0.0, 0.0], [2.0, -1.0], [-5.0, 4.0]], " + "[[-3.0, 0.0], [2.0, 1.0], [0.0, 0.0], [-4.0, 3.0]], " + "[[-5.0, -2.0], [-5.0, -4.0], [-4.0, -3.0], [-6.0, 0.0]]" + "], targets: [0]" + ) + + with pytest.raises(ValueError, match=invalid_observable): + simulator.run(program, shots=shots) + + +@pytest.fixture +def bell_ir_with_result(ir_type): + def _bell_ir_with_result(targets=None): + if ir_type == "Jaqcd": + return JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + ], + "results": [ + {"type": "amplitude", "states": ["11"]}, + { + "type": "expectation", + "observable": ["x"], + "targets": targets, + }, + ], + } + ) + ) + if targets is None: + observable_string = "x all" + elif len(targets) == 1: + observable_string = f"x(q[{targets[0]}])" + else: + raise ValueError("bad test") + + return OpenQASMProgram( + source=f""" + qubit[2] q; + + h q[0]; + cnot q[0], q[1]; + + #pragma braket result amplitude "11" + #pragma braket result expectation {observable_string} + """ + ) + + return _bell_ir_with_result + + +@pytest.fixture +def circuit_noise(ir_type): + if ir_type == "Jaqcd": + return JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + {"type": "bit_flip", "target": 0, "probability": 0.15}, + ] + } + ) + ) + else: + return OpenQASMProgram( + source=""" + OPENQASM 3.0; + qubit[2] q; + h q[0]; + cnot q[0], q[1]; + #pragma braket noise bit_flip(.15) q[0] + """ + ) + + +def test_simulator_identity(caplog): + simulator = StateVectorSimulator() + shots_count = 1000 + programs = ( + JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "i", "target": 0}, + {"type": "i", "target": 1}, + ] + } + ) + ), + OpenQASMProgram( + source=""" + qubit[2] q; + i q; + """ + ), + ) + for program in programs: + if isinstance(program, JaqcdProgram): + result = simulator.run( + program, + qubit_count=2, + shots=shots_count, + ) + else: + result = simulator.run( + program, + shots=shots_count, + ) + counter = Counter( + [ + "".join([str(m) for m in measurement]) + for measurement in result.measurements + ] + ) + assert counter.keys() == {"00"} + assert counter["00"] == shots_count + assert not caplog.text + + +def test_simulator_instructions_not_supported(circuit_noise): + simulator = StateVectorSimulator() + no_noise = re.escape( + "Noise instructions are not supported by the state vector simulator (by default). " + 'You need to use the density matrix simulator: LocalSimulator("braket_dm").' + ) + with pytest.raises(TypeError, match=no_noise): + if isinstance(circuit_noise, JaqcdProgram): + simulator.run(circuit_noise, qubit_count=2, shots=0) + else: + simulator.run(circuit_noise, shots=0) + + +@pytest.mark.xfail(raises=ValueError) +def test_simulator_run_no_results_no_shots(bell_ir): + simulator = StateVectorSimulator() + if isinstance(bell_ir, JaqcdProgram): + simulator.run(bell_ir, qubit_count=2, shots=0) + else: + simulator.run(bell_ir, shots=0) + + +def test_simulator_run_amplitude_shots(): + simulator = StateVectorSimulator() + jaqcd = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [{"type": "h", "target": 0}], + "results": [{"type": "amplitude", "states": ["00"]}], + } + ) + ) + qasm = OpenQASMProgram( + source=""" + qubit q; + h q; + #pragma braket result amplitude "00" + """ + ) + with pytest.raises(ValueError): + simulator.run(jaqcd, qubit_count=2, shots=100) + with pytest.raises(ValueError): + simulator.run(qasm, shots=100) + + +def test_simulator_run_amplitude_no_shots_invalid_states(): + simulator = StateVectorSimulator() + jaqcd = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [{"type": "h", "target": 0}], + "results": [{"type": "amplitude", "states": ["0"]}], + } + ) + ) + qasm = OpenQASMProgram( + source=""" + qubit[2] q; + h q[0]; + i q[1]; + #pragma braket result amplitude "0" + """ + ) + with pytest.raises(ValueError): + simulator.run(jaqcd, qubit_count=2, shots=0) + with pytest.raises(ValueError): + simulator.run(qasm, shots=0) + + +def test_simulator_run_statevector_shots(): + simulator = StateVectorSimulator() + jaqcd = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [{"type": "h", "target": 0}], + "results": [{"type": "statevector"}], + } + ) + ) + qasm = OpenQASMProgram( + source=""" + qubit q; + h q; + #pragma braket result state_vector + """ + ) + with pytest.raises(ValueError): + simulator.run(jaqcd, qubit_count=2, shots=100) + with pytest.raises(ValueError): + simulator.run(qasm, shots=100) + + +def test_simulator_run_result_types_shots(caplog): + simulator = StateVectorSimulator() + jaqcd = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + ], + "results": [ + { + "type": "expectation", + "observable": ["z"], + "targets": [1], + } + ], + } + ) + ) + qasm = OpenQASMProgram( + source=""" + qubit[2] qs; + h qs[0]; + cnot qs[0], qs[1]; + #pragma braket result expectation x(qs[1]) + """ + ) + shots_count = 100 + jaqcd_result = simulator.run(jaqcd, qubit_count=2, shots=shots_count) + qasm_result = simulator.run(qasm, shots=shots_count) + for result in jaqcd_result, qasm_result: + assert all([len(measurement) == 2] for measurement in result.measurements) + assert len(result.measurements) == shots_count + assert result.measuredQubits == [0, 1] + # qasm_result.resultTypes carries info back to the BDK to calculate results + assert not jaqcd_result.resultTypes + assert not caplog.text + + +def test_simulator_run_result_types_shots_basis_rotation_gates(caplog): + simulator = StateVectorSimulator() + jaqcd = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + ], + "basis_rotation_instructions": [{"type": "h", "target": 1}], + "results": [ + { + "type": "expectation", + "observable": ["x"], + "targets": [1], + } + ], + } + ) + ) + qasm = OpenQASMProgram( + source=""" + qubit[2] q; + h q[0]; + cnot q[0], q[1]; + #pragma braket result expectation x(q[1]) + """ + ) + shots_count = 1000 + jaqcd_result = simulator.run(jaqcd, qubit_count=2, shots=shots_count) + qasm_result = simulator.run(qasm, shots=shots_count) + for result in jaqcd_result, qasm_result: + assert all([len(measurement) == 2] for measurement in result.measurements) + assert len(result.measurements) == shots_count + assert result.measuredQubits == [0, 1] + assert not jaqcd_result.resultTypes + assert not caplog.text + + +@pytest.mark.xfail(raises=ValueError) +def test_simulator_run_result_types_shots_basis_rotation_gates_value_error(): + # not a valid computation path for openqasm, since basis rotation instructions + # are calculated from the result types during simulation + simulator = StateVectorSimulator() + ir = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + ], + "basis_rotation_instructions": [{"type": "foo", "target": 1}], + "results": [ + {"type": "expectation", "observable": ["x"], "targets": [1]} + ], + } + ) + ) + shots_count = 1000 + simulator.run(ir, qubit_count=2, shots=shots_count) + + +@pytest.mark.parametrize( + "ir, qubit_count", + [ + ( + JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [{"type": "z", "target": 2}], + "basis_rotation_instructions": [], + "results": [], + } + ) + ), + 1, + ), + ( + JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [{"type": "h", "target": 0}], + "basis_rotation_instructions": [{"type": "z", "target": 3}], + "results": [], + } + ) + ), + 2, + ), + ], +) +@pytest.mark.xfail(raises=ValueError) +def test_simulator_run_non_contiguous_qubits(ir, qubit_count): + # not relevant for openqasm, since it handles qubit allocation + simulator = StateVectorSimulator() + shots_count = 1000 + simulator.run(ir, qubit_count=qubit_count, shots=shots_count) + + +@pytest.mark.parametrize( + "ir, qubit_count", + [ + ( + JaqcdProgram.parse_raw( + json.dumps( + { + "results": [ + {"targets": [2], "type": "expectation", "observable": ["z"]} + ], + "basis_rotation_instructions": [], + "instructions": [{"type": "z", "target": 0}], + } + ) + ), + 1, + ), + ( + JaqcdProgram.parse_raw( + json.dumps( + { + "results": [ + {"targets": [2], "type": "expectation", "observable": ["z"]} + ], + "basis_rotation_instructions": [], + "instructions": [ + {"type": "z", "target": 0}, + {"type": "z", "target": 1}, + ], + } + ) + ), + 2, + ), + ( + OpenQASMProgram( + source=""" + qubit[2] q; + z q; + #pragma braket result expectation z(q[2]) + """ + ), + None, + ), + ], +) +def test_simulator_run_observable_references_invalid_qubit(ir, qubit_count): + simulator = StateVectorSimulator() + shots_count = 0 + if isinstance(ir, JaqcdProgram): + with pytest.raises(ValueError): + simulator.run(ir, qubit_count=qubit_count, shots=shots_count) + else: + # index error since you're indexing from a logical qubit + with pytest.raises(IndexError): + simulator.run(ir, shots=shots_count) + + +@pytest.mark.parametrize("batch_size", [1]) +@pytest.mark.parametrize("targets", [(None), ([1]), ([0])]) +def test_simulator_bell_pair_result_types( + bell_ir_with_result, targets, batch_size, caplog +): + simulator = StateVectorSimulator() + ir = bell_ir_with_result(targets) + if isinstance(ir, JaqcdProgram): + result = simulator.run(ir, qubit_count=2, shots=0, batch_size=batch_size) + else: + result = simulator.run(ir, shots=0, batch_size=batch_size) + assert len(result.resultTypes) == 2 + assert result.resultTypes[0].type == Amplitude(states=["11"]) + assert np.isclose(result.resultTypes[0].value["11"], 1 / np.sqrt(2)) + expected_expectation = Expectation(observable=["x"], targets=targets) + assert result.resultTypes[1].type == expected_expectation + assert np.allclose(result.resultTypes[1].value, 0 if targets else [0, 0]) + assert result.taskMetadata == TaskMetadata( + id=result.taskMetadata.id, + deviceId=StateVectorSimulator.DEVICE_ID, + shots=0, + ) + assert result.additionalMetadata == AdditionalMetadata(action=ir) + assert not caplog.text + + +def test_simulator_fails_samples_0_shots(): + simulator = StateVectorSimulator() + jaqcd = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [{"type": "h", "target": 0}], + "results": [{"type": "sample", "observable": ["x"], "targets": [0]}], + } + ) + ) + qasm = OpenQASMProgram( + source=""" + qubit q; + h q; + #pragma braket result sample x(q) + """ + ) + with pytest.raises(ValueError): + simulator.run(jaqcd, qubit_count=1, shots=0) + with pytest.raises(ValueError): + simulator.run(qasm, shots=0) + + +@pytest.mark.parametrize( + "result_types,expected", + [ + ( + [ + {"type": "expectation", "observable": ["x"], "targets": [1]}, + {"type": "variance", "observable": ["x"], "targets": [1]}, + ], + [0, 1], + ), + ( + [ + {"type": "expectation", "observable": ["x"]}, + {"type": "variance", "observable": ["x"], "targets": [1]}, + ], + [[0, 0], 1], + ), + ( + [ + { + "type": "expectation", + "observable": [[[[0, 0], [1, 0]], [[1, 0], [0, 0]]]], + "targets": [1], + }, + { + "type": "variance", + "observable": [[[[0, 0], [1, 0]], [[1, 0], [0, 0]]]], + "targets": [1], + }, + ], + [0, 1], + ), + ( + [ + { + "type": "expectation", + "observable": ["x", [[[0, 0], [1, 0]], [[1, 0], [0, 0]]]], + "targets": [0, 1], + }, + { + "type": "expectation", + "observable": ["x", [[[0, 0], [1, 0]], [[1, 0], [0, 0]]]], + "targets": [0, 1], + }, + ], + [1, 1], + ), + ( + [ + {"type": "variance", "observable": ["x"], "targets": [1]}, + {"type": "expectation", "observable": ["x"]}, + { + "type": "expectation", + "observable": ["x", [[[0, 0], [1, 0]], [[1, 0], [0, 0]]]], + "targets": [0, 1], + }, + ], + [1, [0, 0], 1], + ), + ], +) +def test_simulator_valid_observables(result_types, expected): + simulator = StateVectorSimulator() + prog = JaqcdProgram.parse_raw( + json.dumps( + { + "instructions": [ + {"type": "h", "target": 0}, + {"type": "cnot", "target": 1, "control": 0}, + ], + "results": result_types, + } + ) + ) + result = simulator.run(prog, qubit_count=2, shots=0) + for i in range(len(result_types)): + assert np.allclose(result.resultTypes[i].value, expected[i]) + + +@pytest.mark.parametrize( + "result_types,expected", + [ + ( + """ + #pragma braket result expectation x(q[1]) + #pragma braket result variance x(q[1]) + """, + [0, 1], + ), + ( + """ + #pragma braket result expectation x all + #pragma braket result variance x(q[1]) + """, + [[0, 0], 1], + ), + ( + """ + #pragma braket result expectation hermitian([[0, 1], [1, 0]]) q[1] + #pragma braket result variance hermitian([[0, 1], [1, 0]]) q[1] + """, + [0, 1], + ), + ( + """ + #pragma braket result expectation x(q[0]) @ hermitian([[0, 1], [1, 0]]) q[1] + #pragma braket result expectation x(q[0]) @ hermitian([[0, 1], [1, 0]]) q[1] + """, + [1, 1], + ), + ( + """ + #pragma braket result variance x(q[1]) + #pragma braket result expectation x all + #pragma braket result expectation x(q[0]) @ hermitian([[0, 1], [1, 0]]) q[1] + """, + [1, [0, 0], 1], + ), + ], +) +def test_simulator_valid_observables_qasm(result_types, expected, caplog): + simulator = StateVectorSimulator() + prog = OpenQASMProgram( + source=f""" + qubit[2] q; + h q[0]; + cnot q[0], q[1]; + {result_types} + """ + ) + result = simulator.run(prog, shots=0) + for i in range(len(result_types.split("\n")) - 2): + assert np.allclose(result.resultTypes[i].value, expected[i]) + assert not caplog.text + + +def test_observable_hash_tensor_product(): + matrix = np.eye(4) + obs = observables.TensorProduct( + [ + observables.PauliX([0]), + observables.Hermitian(matrix, [1, 2]), + observables.PauliY([1]), + ] + ) + hash_dict = StateVectorSimulator._observable_hash(obs) + matrix_hash = hash_dict[1] + assert hash_dict == { + 0: "PauliX", + 1: matrix_hash, + 2: matrix_hash, + 3: "PauliY", + } + + +def test_basis_rotation(caplog): + qasm = """ + qubit q; + qubit[2] qs; + i q; + h qs; + #pragma braket result expectation x(q[0]) + #pragma braket result expectation x(qs[0]) @ i(qs[1]) + #pragma braket result variance x(q[0]) + """ + simulator = StateVectorSimulator() + result = simulator.run(OpenQASMProgram(source=qasm), shots=1000) + measurements = np.array(result.measurements, dtype=int) + assert 400 < np.sum(measurements, axis=0)[0] < 600 + assert np.sum(measurements, axis=0)[1] == 0 + assert 400 < np.sum(measurements, axis=0)[2] < 600 + assert not caplog.text + + +def test_basis_rotation_all(caplog): + qasm = """ + qubit q; + qubit[2] qs; + h q; + h qs; + #pragma braket result variance x all + """ + simulator = StateVectorSimulator() + result = simulator.run(OpenQASMProgram(source=qasm), shots=1000) + measurements = np.array(result.measurements, dtype=int) + assert np.array_equal(measurements, np.zeros([1000, 3])) + + +@pytest.mark.parametrize( + "qasm, error_string", + ( + ( + """ + qubit[2] q; + i q; + #pragma braket result expectation x(q[0]) + // # noqa: E501 + #pragma braket result expectation hermitian([[-6+0im, 2+1im, -3+0im, -5+2im], [2-1im, 0im, 2-1im, -5+4im], [-3+0im, 2+1im, 0im, -4+3im], [-5-2im, -5-4im, -4-3im, -6+0im]]) q[0:1] + """, + "Qubit part of incompatible results targets", + ), + ( + """ + qubit[2] q; + i q; + // # noqa: E501 + // # noqa: E501 + #pragma braket result expectation hermitian([[-6+0im, 2+1im, -3+0im, -5+2im], [2-1im, 0im, 2-1im, -5+4im], [-3+0im, 2+1im, 0im, -4+3im], [-5-2im, -5-4im, -4-3im, -6+0im]]) q[0:1] + // # noqa: E501 + #pragma braket result expectation hermitian([[-5+0im, 2+1im, -3+0im, -5+2im], [2-1im, 0im, 2-1im, -5+4im], [-3+0im, 2+1im, 0im, -4+3im], [-5-2im, -5-4im, -4-3im, -6+0im]]) q[0:1] + """, + "Conflicting result types applied to a single qubit", + ), + ( + """ + qubit[2] q; + i q; + #pragma braket result expectation x(q[0]) + #pragma braket result expectation z(q[0]) @ x(q[1]) + """, + "Conflicting result types applied to a single qubit", + ), + ), +) +def test_partially_overlapping_basis_rotation(qasm, error_string): + with pytest.raises(ValueError, match=error_string): + simulator = StateVectorSimulator() + simulator.run(OpenQASMProgram(source=qasm), shots=1000) + + +def test_sample(caplog): + qasm = """ + qubit[2] qs; + i qs; + #pragma braket result sample x(qs[0]) @ i(qs[1]) + """ + simulator = StateVectorSimulator() + result = simulator.run(OpenQASMProgram(source=qasm), shots=1000) + measurements = np.array(result.measurements, dtype=int) + assert 400 < np.sum(measurements, axis=0)[0] < 600 + assert np.sum(measurements, axis=0)[1] == 0 + assert not caplog.text + + +# def test_adjoint_gradient_pragma_sv1(): +# simulator = StateVectorSimulator() +# prog = OpenQASMProgram( +# source=""" +# input float alpha; +# input float beta; +# qubit[1] q; +# h q[0]; +# #pragma braket result adjoint_gradient h(q[0]) alpha, beta +# """, +# inputs={"alpha": 0.2, "beta": 0.3}, +# ) +# ag_not_supported = "Result type adjoint_gradient is not supported." +# +# with pytest.raises(TypeError, match=ag_not_supported): +# simulator.run(prog, shots=0) + + +def test_missing_input(): + qasm = """ + input int[8] in_int; + int[8] doubled; + + doubled = in_int * 2; + qubit q; + rx(doubled) q; + """ + simulator = StateVectorSimulator() + missing_input = "Missing input variable 'in_int'." + with pytest.raises(NameError, match=missing_input): + simulator.run(OpenQASMProgram(source=qasm), shots=1000) + + +def test_measure_targets(): + qasm = """ + qubit[2] q; + bit[1] b; + h q[0]; + cnot q[0], q[1]; + b[0] = measure q[0]; + """ + simulator = StateVectorSimulator() + result = simulator.run(OpenQASMProgram(source=qasm), shots=1000) + measurements = np.array(result.measurements, dtype=int) + assert 400 < np.sum(measurements, axis=0)[0] < 600 + assert len(measurements[0]) == 1 + assert result.measuredQubits == [0] + + +@pytest.mark.parametrize( + "operation, state_vector", + [ + ["rx(π) q[0];", [0, -1j]], + ["rx(pi) q[0];", [0, -1j]], + ["rx(ℇ) q[0];", [0.21007866, -0.97768449j]], + ["rx(euler) q[0];", [0.21007866, -0.97768449j]], + ["rx(τ) q[0];", [-1, 0]], + ["rx(tau) q[0];", [-1, 0]], + ["rx(pi + pi) q[0];", [-1, 0]], + ["rx(pi - pi) q[0];", [1, 0]], + ["rx(-pi + pi) q[0];", [1, 0]], + ["rx(pi * 2) q[0];", [-1, 0]], + ["rx(pi / 2) q[0];", [0.70710678, -0.70710678j]], + ["rx(-pi / 2) q[0];", [0.70710678, 0.70710678j]], + ["rx(-pi) q[0];", [0, 1j]], + ["rx(pi + 2 * pi) q[0];", [0, 1j]], + ["rx(pi + pi / 2) q[0];", [-0.70710678, -0.70710678j]], + ["rx((pi / 4) + (pi / 2) / 2) q[0];", [0.70710678, -0.70710678j]], + ["rx(0) q[0];", [1, 0]], + ["rx(0 + 0) q[0];", [1, 0]], + ["rx((1.1 + 2.04) / 2) q[0];", [0.70738827, -0.70682518j]], + ["rx((6 - 2.86) * 0.5) q[0];", [0.70738827, -0.70682518j]], + ["rx(pi ** 2) q[0];", [0.22058404, 0.97536797j]], + ], +) +def test_rotation_parameter_expressions(operation, state_vector): + qasm = f""" + OPENQASM 3.0; + bit[1] b; + qubit[1] q; + {operation} + #pragma braket result state_vector + """ + simulator = StateVectorSimulator() + result = simulator.run(OpenQASMProgram(source=qasm), shots=0) + assert result.resultTypes[0].type == StateVector() + assert np.allclose(result.resultTypes[0].value, np.array(state_vector)) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..39cd8a1 --- /dev/null +++ b/tox.ini @@ -0,0 +1,129 @@ +[tox] +envlist = linters,docs,unit-tests,integ-tests + +[testenv:unit-tests] +basepython = python3 +setenv = + JULIA_PKG_USE_CLI_GIT=true + JULIA_CONDAPKG_BACKEND="Null" +# {posargs} contains additional arguments specified when invoking tox. e.g. tox -- -s -k test_foo.py +deps = + {[test-deps]deps} +allowlist_externals = + pytest +commands = + python julia_private_repo_setup/setup_private_repos.py + pytest {posargs} --cov-report term-missing --cov-report html --cov-report xml --cov=braket +extras = test + +[testenv:integ-tests] +basepython = python3 +setenv = + JULIA_PKG_USE_CLI_GIT=true + JULIA_CONDAPKG_BACKEND="Null" +# {posargs} contains additional arguments specified when invoking tox. e.g. tox -- -s -k test_foo.py +deps = + {[test-deps]deps} +allowlist_externals = + pytest +commands = + python julia_private_repo_setup/setup_private_repos.py + pytest {posargs} --cov-report term-missing --cov-report html --cov-report xml --cov=braket +extras = test + +[testenv:linters] +basepython = python3 +skip_install = true +deps = + {[testenv:isort]deps} + {[testenv:black]deps} + {[testenv:flake8]deps} +commands = + # isort MUST come before black as it will revert any changes made by black + {[testenv:isort]commands} + {[testenv:black]commands} + {[testenv:flake8]commands} + +# 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 +deps = + flake8 + flake8-rst-docstrings +commands = + flake8 {posargs} + +[testenv:isort] +basepython = python3 +skip_install = true +deps = + isort +commands = + isort -rc . {posargs} + +[testenv:isort_check] +basepython = python3 +skip_install = true +deps = + isort +commands = + isort . -rc {posargs} + +[testenv:black] +basepython = python3 +skip_install = true +deps = + black +commands = + black ./ {posargs} + +[testenv:black_check] +basepython = python3 +skip_install = true +deps = + black +commands = + black --check ./ {posargs} + +#[testenv:docs] +#basepython = python3 +#deps = +# {[test-deps]deps} +# sphinx +# sphinx-rtd-theme +# sphinxcontrib-apidoc +#commands = +# sphinx-build -E -T -b html doc build/documentation/html + +#[testenv:serve-docs] +#basepython = python3 +#skip_install = true +#changedir = build/documentation/html +#commands = +# python -m http.server {posargs} + +[testenv:zip-build] +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/amazon-braket/amazon-braket-schemas-python.git + git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git +