├── .github ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.md │ ├── ENHANCEMENT_REQUEST.md │ └── FEATURE_REQUEST.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ ├── docs.yml │ ├── pre-commit.yaml │ └── release.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pylintrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.md ├── docs ├── Makefile ├── apidocs │ ├── backends.rst │ ├── client.rst │ ├── exceptions.rst │ ├── helpers.rst │ ├── index.rst │ ├── job.rst │ ├── provider.rst │ └── result.rst ├── conf.py ├── guides │ ├── access.rst │ ├── install.rst │ └── usage.rst └── index.rst ├── example └── example.ipynb ├── make.bat ├── pyproject.toml ├── qiskit_ionq ├── __init__.py ├── constants.py ├── exceptions.py ├── helpers.py ├── ionq_backend.py ├── ionq_client.py ├── ionq_equivalence_library.py ├── ionq_gates.py ├── ionq_job.py ├── ionq_optimizer_plugin.py ├── ionq_provider.py ├── ionq_result.py ├── py.typed ├── rewrite_rules.py └── version.py ├── requirements-docs.txt ├── requirements-test.txt ├── requirements.txt ├── setup.cfg ├── setup.py ├── test ├── __init__.py ├── conftest.py ├── helpers │ ├── __init__.py │ ├── test_gate_serialization.py │ ├── test_helpers.py │ └── test_qiskit_to_ionq.py ├── ionq_backend │ ├── __init__.py │ └── test_base_backend.py ├── ionq_client │ └── __init__.py ├── ionq_gates │ ├── __init__.py │ └── test_gates.py ├── ionq_job │ ├── __init__.py │ └── test_job.py ├── ionq_optimizer_plugin │ ├── __init__.py │ └── test_ionq_optimizer_plugin.py ├── ionq_provider │ ├── __init__.py │ └── test_backend_service.py ├── test_exceptions.py ├── test_mock.py └── transpile_ionq_gates │ ├── __init__.py │ └── test_transpile_ionq_gates.py ├── tools └── verify_headers.py └── tox.ini /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ### Information 6 | 7 | - **Qiskit IonQ Provider version**: 8 | - **API URL or version**: 9 | 10 | - **Python version**: 11 | - **Operating system**: 12 | 13 | ### What is the current behavior? 14 | 15 | 16 | 17 | ### Steps to reproduce the problem 18 | 19 | 20 | 21 | ### What is the expected behavior? 22 | 23 | 24 | 25 | ### Suggested solutions 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 🐛 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'type: bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | ### Information 15 | 16 | - **Qiskit IonQ Provider version**: 17 | - **API URL or version**: 18 | 19 | - **Python version**: 20 | - **Operating system**: 21 | 22 | ### What is the current behavior? 23 | 24 | 25 | 26 | ### Steps to reproduce the problem 27 | 28 | 29 | 30 | ### What is the expected behavior? 31 | 32 | 33 | 34 | ### Suggested solutions 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ENHANCEMENT_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement request ✨ 3 | about: Suggest an improvement for this project 4 | labels: 'type: enhancement' 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | ### What is the desired enhancement? 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 💡 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'type: feature request' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | 15 | ### What feature would you like to see added? Why is it valuable? 16 | 17 | ### What is the expected behavior, in detail? 18 | 19 | ### Can you help make this feature a reality? By yourself? If not, what support would you need? 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | ### Summary 14 | 15 | ### Details and comments 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: pr 2 | on: 3 | pull_request: {} 4 | push: 5 | branches: 6 | - main 7 | permissions: 8 | contents: read 9 | jobs: 10 | tests: 11 | name: python-${{ matrix.python-version }}-${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 16 | os: ["macOS-latest", "ubuntu-latest", "windows-latest"] 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | persist-credentials: false 21 | - name: setup-python-${{ matrix.python-version }} 22 | uses: actions/setup-python@v5 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | - name: install-requirements 26 | run: | 27 | python -m pip install --upgrade pip tox tox-gh-actions 28 | python -m pip install -e . 29 | - name: run-tests 30 | run: tox 31 | lint: 32 | name: lint 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | with: 37 | persist-credentials: false 38 | - name: setup-python-3.12 39 | uses: actions/setup-python@v5 40 | with: 41 | python-version: 3.12 42 | - name: install-requirements 43 | run: | 44 | python -m pip install --upgrade pip tox tox-gh-actions 45 | python -m pip install -e . 46 | - name: lint 47 | run: tox -e lint 48 | docs: 49 | name: docs 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v4 53 | with: 54 | persist-credentials: false 55 | - name: setup-python-3.13 56 | uses: actions/setup-python@v5 57 | with: 58 | python-version: 3.13 59 | - name: install-requirements 60 | run: | 61 | python -m pip install --upgrade pip tox tox-gh-actions 62 | python -m pip install -e . 63 | - name: docs 64 | run: tox -e docs 65 | - uses: actions/upload-artifact@v4 66 | if: ${{ github.event_name == 'pull_request' }} 67 | with: 68 | name: html_docs 69 | path: build/html 70 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Docs Publish 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - "*" 8 | 9 | 10 | jobs: 11 | deploy: 12 | permissions: 13 | contents: write 14 | pages: write 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | persist-credentials: false 21 | - name: Set up Python 22 | uses: actions/setup-python@v5 23 | with: 24 | python-version: '3.13' 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | pip install -U virtualenv setuptools wheel tox 29 | sudo apt-get install -y graphviz pandoc 30 | - name: Build docs 31 | run: tox -edocs 32 | - name: Bypass Jekyll Processing # Necessary for setting the correct css path 33 | run: touch build/html/.nojekyll 34 | - name: Deploy 35 | uses: JamesIves/github-pages-deploy-action@v4 36 | with: 37 | folder: build/html/ 38 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yaml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | pre-commit: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | lfs: true 17 | persist-credentials: false 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: "3.x" 23 | 24 | - name: Install pre-commit 25 | run: pip install pre-commit 26 | 27 | - name: Run pre-commit 28 | run: pre-commit run --all-files --show-diff-on-failure 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - "*" 6 | permissions: 7 | contents: read 8 | jobs: 9 | publish-to-pypi: 10 | name: publish-to-pypi 11 | runs-on: ubuntu-latest 12 | permissions: 13 | id-token: write 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | persist-credentials: false 18 | - uses: actions/setup-python@v5 19 | name: install-python 20 | with: 21 | python-version: 3.13 22 | - name: install-deps 23 | run: | 24 | pip install -U setuptools wheel build 25 | - name: build 26 | shell: bash 27 | run: | 28 | python -m build 29 | - uses: actions/upload-artifact@v4 30 | with: 31 | path: ./dist/qiskit* 32 | - name: Publish package to PyPI 33 | uses: pypa/gh-action-pypi-publish@release/v1 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SDK config file 2 | Qconfig.py 3 | 4 | # ply outputs 5 | qiskit/qasm/parser.out 6 | 7 | # editor files 8 | .vscode/ 9 | .idea/ 10 | 11 | #standard python ignores follow 12 | 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | .pytest_cache/ 16 | *.py[cod] 17 | *$py.class 18 | 19 | # C extensions 20 | *.so 21 | 22 | # Distribution / packaging 23 | .Python 24 | env/ 25 | build/ 26 | develop-eggs/ 27 | dist/ 28 | downloads/ 29 | eggs/ 30 | .eggs/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | *.egg-info/ 36 | .installed.cfg 37 | *.egg 38 | 39 | # PyInstaller 40 | # Usually these files are written by a python script from a template 41 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 42 | *.manifest 43 | *.spec 44 | 45 | # Installer logs 46 | pip-log.txt 47 | pip-delete-this-directory.txt 48 | 49 | # Unit test / coverage reports 50 | htmlcov/ 51 | .tox/ 52 | .coverage 53 | .coverage.* 54 | .cache 55 | nosetests.xml 56 | coverage.xml 57 | *,cover 58 | .hypothesis/ 59 | test/python/*.log 60 | test/python/*.pdf 61 | test/python/*.prof 62 | .stestr/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Django stuff: 69 | *.log 70 | local_settings.py 71 | 72 | # Flask stuff: 73 | instance/ 74 | .webassets-cache 75 | 76 | # Scrapy stuff: 77 | .scrapy 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # PyBuilder 83 | target/ 84 | 85 | # Jupyter Notebook 86 | .ipynb_checkpoints 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # celery beat schedule file 92 | celerybeat-schedule 93 | 94 | # SageMath parsed files 95 | *.sage.py 96 | 97 | # dotenv 98 | .env 99 | 100 | # virtualenv 101 | .venv 102 | venv/ 103 | ENV/ 104 | 105 | # Spyder project settings 106 | .spyderproject 107 | 108 | # Rope project settings 109 | .ropeproject 110 | 111 | .DS_Store 112 | 113 | # editor ignores 114 | .vscode/ 115 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-docstring-first 8 | - id: check-yaml 9 | - id: debug-statements 10 | - id: check-ast 11 | - repo: https://github.com/astral-sh/ruff-pre-commit 12 | rev: "v0.8.2" 13 | hooks: 14 | - id: ruff 15 | exclude: docs/conf.py|tools/verify_headers.py 16 | args: ["--fix"] 17 | - id: ruff-format 18 | - repo: https://github.com/woodruffw/zizmor-pre-commit 19 | rev: v0.8.0 20 | hooks: 21 | - id: zizmor 22 | - repo: https://github.com/pre-commit/mirrors-mypy 23 | rev: "v1.13.0" 24 | hooks: 25 | - id: mypy 26 | exclude: docs/conf.py 27 | additional_dependencies: 28 | - types-requests 29 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | The Qiskit Community is dedicated to our values of treating every individual 6 | with respect and dignity. In the interest of fostering an open and welcoming 7 | environment, all participants, including attendees, speakers, sponsors, 8 | volunteers, online contributors, and IBM employees are expected to show 9 | courtesy for each other and our community by creating a harassment-free 10 | experience for everyone, regardless of age, personal appearance, disability, 11 | ethnicity, gender identity and expression, body size, level of experience, 12 | nationality, race, religion, caste, or sexual identity and orientation. 13 | Expected behavior applies to both online and offline engagement within the 14 | Qiskit Community. 15 | 16 | ## Scope 17 | 18 | The purpose of this Code of Conduct is to define and enforce the values and 19 | conduct of contributors and participants in the Qiskit open source community. 20 | The Code of Conduct applies both within project spaces and in public spaces 21 | when an individual is engaging with the Qiskit open source community. Examples 22 | include attending a Qiskit event, contributing to online projects, commentary 23 | on Slack, or representing a project or community, including using an official 24 | project e-mail address, posting via an official social media account, or 25 | acting as an appointed representative at an online or offline event. 26 | Representation of a project may be further defined and clarified by project 27 | maintainers. 28 | 29 | ## Our Standards 30 | 31 | Examples of behavior that contributes to creating a positive environment 32 | include: 33 | 34 | - Using welcoming and inclusive language 35 | - Being respectful of differing viewpoints, experiences, and cultures 36 | - Gracefully accepting constructive criticism 37 | - Focusing on what is best for the community 38 | - Showing empathy towards other community members 39 | - Being mindful of your surroundings and your fellow participants and listening 40 | to others 41 | - Valuing the contributions of all participants 42 | - Engaging in collaboration before conflict 43 | - Pointing out unintentionally racist, sexist, casteist, or biased comments and 44 | jokes made by community members when they happen 45 | 46 | Examples of unacceptable behavior by participants, even when presented as 47 | "ironic" or "joking," include: 48 | 49 | - The use of sexualized language or imagery and unwelcome physical contact, 50 | sexual attention, or advances 51 | - Trolling, insulting/derogatory comments, and personal or political attacks 52 | - Public or private harassment, including offensive or degrading language 53 | - Publishing others' private information, such as a physical or electronic 54 | address, without explicit permission. This includes any sort of "outing" of 55 | any aspect of someone's identity without their consent. 56 | - "Doxxing," Publishing screenshots or quotes, especially from identity slack 57 | channels, private chat, or public events, without all quoted users' explicit 58 | consent. 59 | - Other conduct which could reasonably be considered inappropriate in a 60 | professional setting 61 | 62 | ## Responsibilities & Enforcement 63 | 64 | The entire Qiskit community is responsible for upholding the terms of the Code 65 | of Conduct in Qiskit Community events and spaces and reporting violations if 66 | they see them. The internal Qiskit team at IBM is ultimately responsible for 67 | clarifying the standards of acceptable behavior and enforcement, and is expected 68 | to take appropriate and fair corrective action in response to any instances of 69 | unacceptable behavior. 70 | 71 | If a participant or contributor engages in negative or harmful behavior, IBM 72 | will take any action they deem appropriate, including but not limited to 73 | issuing warnings, expulsion from an event with no refund, deleting comments, 74 | permanent banning from future events or online community, or calling local law 75 | enforcement. IBM has the right and responsibility to remove, edit, or reject 76 | comments, commits, code, wiki edits, issues, and other contributions that are 77 | not aligned to this Code of Conduct, or to temporarily or permanently ban any 78 | contributor or participant for other behaviors that they deem inappropriate, 79 | threatening, offensive, or harmful. 80 | 81 | If you see a Code of Conduct violation: 82 | 83 | 1. If you feel comfortable, let the person know that what they did is not 84 | appropriate and ask them to stop and/or edit or delete their message(s) or 85 | comment(s). 86 | 2. If the person does not immediately stop the behavior or correct the issue, 87 | or if you're uncomfortable speaking up, flag a moderator and, if appropriate, 88 | fill out the anonymous 89 | [Code of Conduct violation form](https://airtable.com/shrl5mEF4Eun1aIDm). 90 | 3. The Qiskit Community will open an investigation upon receiving your form 91 | entry. When reporting, please include any relevant details, links, 92 | screenshots, context, or other information that may be used to better 93 | understand and resolve the situation. 94 | 4. If the code of conduct violation occurs at an event and requires immediate 95 | response or contains a concern about an individual attending an upcoming 96 | event, contact the event's on-call Code of Conduct point of contact listed 97 | in the event specific code of conduct document. If you don't feel comfortable 98 | speaking to the point of contact in person, fill out a Code of Conduct 99 | violation form entry and include the details of the event so that the Code of 100 | Conduct enforcement board can contact the event's on-call Code of Conduct 101 | point of contact. 102 | 5. If an IBM employee witnesses a Code of Conduct violation at any time, such as 103 | at events, in a Slack channel, or open source forums, it is their 104 | responsibility to file a Code of Conduct violation report. 105 | 106 | This Code of Conduct does not supersede existing IBM corporate policies, such as 107 | the IBM Business Conduct Guidelines and IBM Business Partner Code of Conduct. 108 | IBM employees must follow IBM's Business Conduct Guidelines. IBM's business 109 | partners must follow the IBM Business Partner Code of Conduct. IBM employees 110 | concerned with a fellow IBMer's behavior should follow IBM's own internal HR 111 | reporting protocols, which include engaging the offending IBMer's manager and 112 | involving IBM Concerns and Appeals. IBM employees concerned with an IBM 113 | business partner's behavior should notify tellibm@us.ibm.com. 114 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First read the overall project contributing guidelines. These are all 4 | included in the qiskit documentation: 5 | 6 | https://github.com/Qiskit/qiskit/blob/main/CONTRIBUTING.md 7 | 8 | ## Contributing to Qiskit IonQ Provider 9 | 10 | ### Getting started 11 | 12 | All contributions are welcome. 13 | 14 | If you've noticed a bug or have a feature request, we encourage to open an issue in [this repo's issues tracker](https://github.com/qiskit-partners/qiskit-ionq/issues), whether or not you plan to address it yourself. 15 | 16 | If you intend to contribute code, please still start the contribution process by opening a new issue or making a comment on an existing issue briefly explaining what you intend to address and how. This helps us understand your intent/approach and provide support and commentary before you take the time to actually write code, and ensures multiple people aren't accidentally working on the same thing. 17 | 18 | ### Making a pull request 19 | 20 | When you're ready to make a pull request, please make sure the following is true: 21 | 22 | 1. The code matches the project's code style 23 | 2. The documentation, _including any docstrings for changed methods_, has been updated 24 | 3. If appropriate for your change, that new tests have been added to address any new functionality, or that existing tests have been updated as appropriate 25 | 4. All of the tests (new and old) still pass! 26 | 5. You have added notes in the pull request that explains what has changed and links to the relevant issues in the issues tracker 27 | 28 | ### Running the tests 29 | 30 | This package uses the [pytest](https://docs.pytest.org/en/stable/) test runner. 31 | 32 | To use pytest directly, just run: 33 | 34 | ```bash 35 | pytest [pytest-args] 36 | ``` 37 | 38 | Alternatively, you may also use setuptools integration by running tests through `setup.py`, e.g.: 39 | 40 | ```bash 41 | python setup.py test --addopts="[pytest-args]" 42 | ``` 43 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 IBM and its contributors 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright 2020 IBM and its contributors. 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include . *.txt 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = docs 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qiskit IonQ Provider 2 | 3 | IonQ Logo 4 | 5 | [![License](https://img.shields.io/github/license/qiskit-community/qiskit-aqt-provider.svg?style=popout-square)](https://opensource.org/licenses/Apache-2.0) 6 | 7 | **Qiskit** is an open-source SDK for working with quantum computers at the level of circuits, algorithms, and application modules. 8 | 9 | This project contains a provider that allows access to **[IonQ]** ion trap quantum 10 | systems. 11 | 12 | The example python notebook (in `/example`) should help you understand basic usage. 13 | 14 | ## API Access 15 | 16 | The IonQ Provider uses IonQ's REST API, and using the provider requires an API access token from IonQ. If you would like to use IonQ as a Qiskit provider, please visit to generate an IonQ API key. 17 | 18 | ## Installation 19 | 20 | You can install the provider using pip: 21 | 22 | ```bash 23 | pip install qiskit-ionq 24 | ``` 25 | 26 | ## Provider Setup 27 | 28 | To instantiate the provider, make sure you have an access token then create a provider: 29 | 30 | ```python 31 | from qiskit_ionq import IonQProvider 32 | 33 | provider = IonQProvider("token") 34 | ``` 35 | 36 | ### Credential Environment Variables 37 | 38 | Alternatively, the IonQ Provider can discover your access token from environment variables: 39 | 40 | ```bash 41 | export IONQ_API_TOKEN="token" 42 | ``` 43 | 44 | Then invoke instantiate the provider without any arguments: 45 | 46 | ```python 47 | from qiskit_ionq import IonQProvider, ErrorMitigation 48 | 49 | provider = IonQProvider() 50 | ``` 51 | 52 | Once the provider has been instantiated, it may be used to access supported backends: 53 | 54 | ```python 55 | # Show all current supported backends: 56 | print(provider.backends()) 57 | 58 | # Get IonQ's simulator backend: 59 | simulator_backend = provider.get_backend("ionq_simulator") 60 | ``` 61 | 62 | ### Submitting a Circuit 63 | 64 | Once a backend has been specified, it may be used to submit circuits. 65 | For example, running a Bell State: 66 | 67 | ```python 68 | from qiskit import QuantumCircuit 69 | 70 | # Create a basic Bell State circuit: 71 | qc = QuantumCircuit(2, 2) 72 | qc.h(0) 73 | qc.cx(0, 1) 74 | qc.measure([0, 1], [0, 1]) 75 | 76 | # Run the circuit on IonQ's platform with error mitigation: 77 | job = simulator_backend.run(qc, error_mitigation=ErrorMitigation.DEBIASING) 78 | 79 | # Print the results. 80 | print(job.result().get_counts()) 81 | 82 | # Get results with a different aggregation method when debiasing 83 | # is applied as an error mitigation strategy 84 | print(job.result(sharpen=True).get_counts()) 85 | 86 | # The simulator specifically provides the the ideal probabilities and creates 87 | # counts by sampling from these probabilities. The raw probabilities are also accessible: 88 | print(job.result().get_probabilities()) 89 | ``` 90 | 91 | ### Basis gates and transpilation 92 | 93 | The IonQ provider provides access to the full IonQ Cloud backend, which includes its own transpilation and compilation pipeline. As such, IonQ provider backends have a broad set of "basis gates" that they will accept — effectively anything the IonQ API will accept. The current supported gates can be found [on our docs site](https://docs.ionq.com/#tag/quantum_programs). 94 | 95 | If you have circuits that you'd like to run on IonQ backends that use other gates than this (`u` or `iswap` for example), you will either need to manually rewrite the circuit to only use the above list, or use the Qiskit transpiler, per the example below. Please note that not all circuits can be automatically transpiled. 96 | 97 | If you'd like lower-level access—the ability to program in native gates and skip our compilation/transpilation pipeline—please reach out to your IonQ contact for further information. 98 | 99 | ```python 100 | from qiskit import QuantumCircuit, transpile 101 | from math import pi 102 | 103 | qc2 = QuantumCircuit(1, 1) 104 | qc2.u(pi, pi/2, pi/4, 0) 105 | qc2.measure(0,0) 106 | transpiled_circuit = transpile(qc2, simulator_backend) 107 | ``` 108 | 109 | ## Contributing 110 | 111 | If you'd like to contribute to the IonQ Provider, please take a look at the [contribution guidelines](CONTRIBUTING.md). This project adheres the Qiskit Community code of conduct. By participating, you are agreeing to uphold this code. 112 | 113 | If you have an enhancement request or bug report, we encourage you to open an issue in [this repo's issues tracker](https://github.com/qiskit-partners/qiskit-ionq/issues). If you have a support question or general discussion topic, we recommend instead asking on the [Qiskit community slack](https://qiskit.slack.com/) (you can join using [this link](https://ibm.co/joinqiskitslack)) or the [Quantum Computing StackExchange](https://quantumcomputing.stackexchange.com/questions/tagged/qiskit). 114 | 115 | ## Running Tests 116 | 117 | This package uses the [pytest](https://docs.pytest.org/en/stable/) test runner, and other packages 118 | for mocking interfactions, reporting coverage, etc. 119 | These can be installed with `pip install -r requirements-test.txt`. 120 | 121 | To use pytest directly, just run: 122 | 123 | ```bash 124 | pytest [pytest-args] 125 | ``` 126 | 127 | Alternatively, you may use the setuptools integration by running tests through `setup.py`, e.g.: 128 | 129 | ```bash 130 | python setup.py test --addopts="[pytest-args]" 131 | ``` 132 | 133 | ### Fixtures 134 | 135 | Global pytest fixtures for the test suite can be found in the top-level [test/conftest.py](./test/conftest.py) file. 136 | 137 | ## SSL certificate issues 138 | 139 | If you receive the error `SSLError(SSLCertVerificationError)` or otherwise are unable to connect succesfully, there are a few possible resolutions: 140 | 141 | 1. Try accessing in your browser; if this does not load, you need to contact an IT administrator about allowing IonQ API access. 142 | 2. `pip install pip_system_certs` instructs python to use the same certificate roots of trust as your local browser - install this if the first step succeeded but qiskit-ionq continues to have issues. 143 | 3. You can debug further by running `res = requests.get('https://api.ionq.co/v0.3/health', timeout=30)` and inspecting `res`, you should receive a 200 response with the content `{"status": "pass"}`. If you see a corporate or ISP login page, you will need to contact a local IT administrator to debug further. 144 | 145 | ## Documentation 146 | 147 | To build the API reference and quickstart docs, run: 148 | 149 | ```bash 150 | pip install -r requirements-docs.txt 151 | make html 152 | open build/html/index.html 153 | ``` 154 | 155 | ## License 156 | 157 | [Apache License 2.0]. 158 | 159 | The IonQ logo and Q mark are copyright IonQ, Inc. All rights reserved. 160 | 161 | [ionq]: https://www.ionq.com/ 162 | [apache license 2.0]: https://github.com/qiskit-partners/qiskit-ionq/blob/master/LICENSE.txt 163 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2018, 2021. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # You can set these variables from the command line. 14 | SPHINXOPTS = 15 | SPHINXBUILD = sphinx-build 16 | SOURCEDIR = . 17 | BUILDDIR = _build 18 | 19 | # Put it first so that "make" without argument is like "make help". 20 | help: 21 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 22 | 23 | .PHONY: help Makefile 24 | 25 | # Catch-all target: route all unknown targets to Sphinx using the new 26 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 27 | %: Makefile 28 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 29 | -------------------------------------------------------------------------------- /docs/apidocs/backends.rst: -------------------------------------------------------------------------------- 1 | .. _backends: 2 | 3 | IonQ Backends 4 | ============= 5 | 6 | .. automodule:: qiskit_ionq.ionq_backend 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/apidocs/client.rst: -------------------------------------------------------------------------------- 1 | .. _client: 2 | 3 | API Client 4 | ========== 5 | 6 | .. automodule:: qiskit_ionq.ionq_client 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/apidocs/exceptions.rst: -------------------------------------------------------------------------------- 1 | .. _exceptions: 2 | 3 | Exceptions 4 | ========== 5 | 6 | .. automodule:: qiskit_ionq.exceptions 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/apidocs/helpers.rst: -------------------------------------------------------------------------------- 1 | .. _helpers: 2 | 3 | Helpers 4 | ======= 5 | 6 | .. automodule:: qiskit_ionq.helpers 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/apidocs/index.rst: -------------------------------------------------------------------------------- 1 | .. module:: apireference 2 | 3 | Qiskit IonQ Provider API Reference 4 | ================================== 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | job 10 | provider 11 | backends 12 | client 13 | result 14 | exceptions 15 | helpers 16 | -------------------------------------------------------------------------------- /docs/apidocs/job.rst: -------------------------------------------------------------------------------- 1 | .. _job: 2 | 3 | IonQ Job 4 | ======== 5 | 6 | .. automodule:: qiskit_ionq.ionq_job 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/apidocs/provider.rst: -------------------------------------------------------------------------------- 1 | .. _provider: 2 | 3 | IonQ Provider 4 | ============= 5 | 6 | .. automodule:: qiskit_ionq.ionq_provider 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/apidocs/result.rst: -------------------------------------------------------------------------------- 1 | .. _result: 2 | 3 | IonQ Result 4 | =========== 5 | 6 | .. automodule:: qiskit_ionq.ionq_result 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | # Configuration file for the Sphinx documentation builder. 28 | # 29 | # This file only contains a selection of the most common options. For a full 30 | # list see the documentation: 31 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 32 | 33 | # -- Path setup -------------------------------------------------------------- 34 | 35 | # If extensions (or modules to document with autodoc) are in another directory, 36 | # add these directories to sys.path here. If the directory is relative to the 37 | # documentation root, use os.path.abspath to make it absolute, like shown here. 38 | # 39 | # import os 40 | # import sys 41 | # sys.path.insert(0, os.path.abspath('.')) 42 | 43 | """Sphinx doc build configuration.""" 44 | 45 | import qiskit_sphinx_theme 46 | from qiskit_ionq.version import VERSION_INFO 47 | 48 | # -- Project information ----------------------------------------------------- 49 | 50 | project = "Qiskit IonQ Provider" # pylint: disable=invalid-name 51 | copyright = "2020, IonQ, Inc." # pylint: disable=invalid-name,redefined-builtin 52 | author = "IonQ, Inc." # pylint: disable=invalid-name 53 | release = VERSION_INFO # pylint: disable=invalid-name 54 | 55 | 56 | # -- General configuration --------------------------------------------------- 57 | 58 | # Add any Sphinx extension module names here, as strings. They can be 59 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 60 | # ones. 61 | extensions = [ 62 | "sphinx.ext.napoleon", 63 | "sphinx.ext.autodoc", 64 | "sphinx.ext.autosummary", 65 | "sphinx.ext.mathjax", 66 | "sphinx.ext.viewcode", 67 | "sphinx.ext.extlinks", 68 | "jupyter_sphinx", 69 | "sphinx_panels", 70 | "qiskit_sphinx_theme", 71 | ] 72 | 73 | # Add any paths that contain templates here, relative to this directory. 74 | templates_path = ["_templates"] 75 | 76 | # List of patterns, relative to source directory, that match files and 77 | # directories to ignore when looking for source files. 78 | # This pattern also affects html_static_path and html_extra_path. 79 | exclude_patterns = ["_build", "**.ipynb_checkpoints"] 80 | 81 | 82 | # -- Options for HTML output ------------------------------------------------- 83 | 84 | # The theme to use for HTML and HTML Help pages. See the documentation for 85 | # a list of builtin themes. 86 | html_theme = "qiskit-ecosystem" 87 | html_title = f"{project} {release}" 88 | 89 | autosummary_generate = True 90 | autosummary_generate_overwrite = False 91 | 92 | autodoc_default_options = { 93 | "inherited-members": None, 94 | } 95 | 96 | autoclass_content = "both" 97 | # 98 | # Sphinx doc mappings 99 | intersphinx_mapping = { 100 | "qiskit-terra": ("https://docs.quantum.ibm.com/api/qiskit/", None), 101 | "requests": ("https://requests.readthedocs.io/en/master/", None), 102 | } 103 | -------------------------------------------------------------------------------- /docs/guides/access.rst: -------------------------------------------------------------------------------- 1 | IonQ API Access 2 | =============== 3 | 4 | If you would like to use IonQ as a Qiskit provider, please create an account 5 | at https://cloud.ionq.com/ and generate a key from API Keys under your profile. 6 | -------------------------------------------------------------------------------- /docs/guides/install.rst: -------------------------------------------------------------------------------- 1 | Installing the Qiskit IonQ Provider 2 | =================================== 3 | 4 | .. NOTE:: 5 | 6 | **The Qiskit IonQ Provider requires** ``qiskit-terra>=0.17.4``! 7 | 8 | To ensure you are on the latest version, run:: 9 | 10 | pip install -U "qiskit-terra>=0.17.4" 11 | 12 | 13 | You can install the provider using pip:: 14 | 15 | pip install qiskit-ionq 16 | 17 | Provider Setup 18 | -------------- 19 | 20 | The IonQ Provider uses IonQ's REST API. 21 | 22 | To instantiate the provider, make sure you have an access token then create a provider:: 23 | 24 | 25 | from qiskit_ionq import IonQProvider 26 | 27 | provider = IonQProvider("token") 28 | 29 | 30 | Credential Environment Variable 31 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 32 | 33 | Alternatively, the IonQ Provider can discover your access token using the ``IONQ_API_TOKEN`` environment variable:: 34 | 35 | export IONQ_API_TOKEN="token" 36 | 37 | Then instantiate the provider without any arguments:: 38 | 39 | from qiskit_ionq import IonQProvider 40 | 41 | provider = IonQProvider() 42 | -------------------------------------------------------------------------------- /docs/guides/usage.rst: -------------------------------------------------------------------------------- 1 | ==================================== 2 | Submitting circuits to IonQ backends 3 | ==================================== 4 | 5 | Once a a backend has been specified, it may be used to submit circuits. 6 | For example, running a Bell State: 7 | 8 | .. code-block:: python 9 | 10 | from qiskit import QuantumCircuit 11 | 12 | # Create a basic Bell State circuit: 13 | qc = QuantumCircuit(2, 2) 14 | qc.h(0) 15 | qc.cx(0, 1) 16 | qc.measure([0, 1], [0, 1]) 17 | 18 | # Run the circuit on IonQ's platform: 19 | job = simulator_backend.run(qc) 20 | 21 | # Print the results: 22 | print(job.get_counts()) 23 | 24 | 25 | Basis gates and transpilation 26 | ============================= 27 | 28 | The IonQ provider provides access to the full IonQ Cloud backend, which includes 29 | its own transpilation and compilation pipeline. As such, IonQ provider backends 30 | have a broad set of "basis gates" that they will accept — effectively anything 31 | the IonQ API will accept: 32 | 33 | .. jupyter-execute:: 34 | :hide-code: 35 | 36 | from qiskit_ionq import IonQProvider 37 | ionq = IonQProvider('TOKEN') 38 | print(ionq.backends.ionq_qpu.configuration().basis_gates) 39 | 40 | 41 | If you have circuits that you'd like to run on IonQ backends that use other gates 42 | than this (``u`` or ``iswap`` for example), you will either need to manually rewrite 43 | the circuit to only use the above list, or use the Qiskit transpiler, per the 44 | example below. Please note that not all circuits can be automatically transpiled. 45 | 46 | If you'd like lower-level access—the ability to program in native gates and skip 47 | our compilation/transpilation pipeline—please reach out to your IonQ contact for 48 | further information. 49 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | ================================== 4 | Qiskit IonQ Provider documentation 5 | ================================== 6 | 7 | .. toctree:: 8 | :hidden: 9 | 10 | Home 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | 15 | Installation 16 | IonQ API Access 17 | Submitting circuits 18 | 19 | 20 | .. toctree:: 21 | :maxdepth: 1 22 | :caption: API Documentation 23 | :hidden: 24 | 25 | API Reference 26 | 27 | 28 | 29 | Indices and tables 30 | ================== 31 | 32 | * :ref:`genindex` 33 | * :ref:`modindex` 34 | * :ref:`search` 35 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.isort] 6 | sections = ['FUTURE', 'STDLIB', 'THIRDPARTY', 'FIRSTPARTY', 'LOCALFOLDER'] 7 | -------------------------------------------------------------------------------- /qiskit_ionq/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Provider for IonQ backends""" 28 | 29 | # warn if qiskit is not installed 30 | try: 31 | from qiskit.version import get_version_info # pylint: disable=unused-import # noqa: F401 32 | except ImportError as exc: 33 | raise ImportError( 34 | "Qiskit is not installed. Please install the latest version of Qiskit." 35 | ) from exc 36 | 37 | from .ionq_provider import IonQProvider 38 | from .ionq_optimizer_plugin import ( 39 | TrappedIonOptimizerPlugin, 40 | TrappedIonOptimizerPluginSimpleRules, 41 | TrappedIonOptimizerPluginCompactGates, 42 | TrappedIonOptimizerPluginCommuteGpi2ThroughMs, 43 | ) 44 | from .version import __version__ 45 | from .ionq_gates import GPIGate, GPI2Gate, MSGate, ZZGate 46 | from .constants import ErrorMitigation 47 | from .ionq_equivalence_library import add_equivalences 48 | 49 | __all__ = [ 50 | "__version__", 51 | "IonQProvider", 52 | "TrappedIonOptimizerPlugin", 53 | "TrappedIonOptimizerPluginSimpleRules", 54 | "TrappedIonOptimizerPluginCompactGates", 55 | "TrappedIonOptimizerPluginCommuteGpi2ThroughMs", 56 | "GPIGate", 57 | "GPI2Gate", 58 | "MSGate", 59 | "ZZGate", 60 | "ErrorMitigation", 61 | "add_equivalences", 62 | ] 63 | -------------------------------------------------------------------------------- /qiskit_ionq/constants.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Constants used by components of the provider.""" 28 | 29 | import enum 30 | 31 | from qiskit.providers import jobstatus 32 | 33 | 34 | class APIJobStatus(enum.Enum): 35 | """Job status values. 36 | 37 | Happy path job flow is 38 | SUBMITTED -> READY -> RUNNING -> COMPLETED 39 | """ 40 | 41 | SUBMITTED = "submitted" 42 | READY = "ready" 43 | RUNNING = "running" 44 | CANCELED = "canceled" 45 | COMPLETED = "completed" 46 | FAILED = "failed" 47 | 48 | 49 | class JobStatusMap(enum.Enum): 50 | """Enum to map IonQ job statuses to a qiskit JobStatus. 51 | 52 | IonQ Status Transition happy path: 53 | SUBMITTED -> READY -> RUNNING -> COMPLETED 54 | 55 | Qiskit Status Transition happy path: 56 | INITIALIZING -> QUEUED -> RUNNING -> DONE 57 | """ 58 | 59 | SUBMITTED = jobstatus.JobStatus.INITIALIZING.name 60 | READY = jobstatus.JobStatus.QUEUED.name 61 | RUNNING = jobstatus.JobStatus.RUNNING.name 62 | CANCELED = jobstatus.JobStatus.CANCELLED.name 63 | COMPLETED = jobstatus.JobStatus.DONE.name 64 | FAILED = jobstatus.JobStatus.ERROR.name 65 | 66 | 67 | class ErrorMitigation(enum.Enum): 68 | """Class for error mitigation settings enumerated type.""" 69 | 70 | DEBIASING = {"debias": True} 71 | NO_DEBIASING = {"debias": False} 72 | 73 | 74 | __all__ = ["APIJobStatus", "JobStatusMap"] 75 | -------------------------------------------------------------------------------- /qiskit_ionq/exceptions.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Exceptions for the IonQ Provider.""" 28 | 29 | from __future__ import annotations 30 | 31 | from typing import Literal 32 | 33 | import json.decoder as jd 34 | 35 | import requests 36 | 37 | from qiskit.exceptions import QiskitError 38 | from qiskit.providers import JobError, JobTimeoutError 39 | 40 | 41 | class IonQError(QiskitError): 42 | """Base class for errors raised by an IonQProvider.""" 43 | 44 | def __str__(self) -> str: 45 | return f"{self.__class__.__name__}({self.message!r})" 46 | 47 | def __repr__(self) -> str: 48 | return repr(str(self)) 49 | 50 | 51 | class IonQCredentialsError(IonQError): 52 | """Errors generated from bad credentials or config""" 53 | 54 | 55 | class IonQClientError(IonQError): 56 | """Errors that arise from unexpected behavior while using IonQClient.""" 57 | 58 | 59 | class IonQRetriableError(IonQError): 60 | """Errors that do not indicate a failure related to the request, and can be retried.""" 61 | 62 | def __init__(self, cause): 63 | self._cause = cause 64 | super().__init__(getattr(cause, "message", "Unknown error")) 65 | 66 | 67 | # pylint: disable=no-member 68 | 69 | 70 | # https://support.cloudflare.com/hc/en-us/articles/115003014512-4xx-Client-Error 71 | # "Cloudflare will generate and serve a 409 response for a Error 1001: DNS Resolution Error." 72 | # We may want to condition on the body as well, to allow for some GET requests to return 409 in 73 | # the future. 74 | _RETRIABLE_FOR_GETS = {requests.codes.conflict} 75 | # Retriable regardless of the source 76 | # Handle 52x responses from cloudflare. 77 | # See https://support.cloudflare.com/hc/en-us/articles/115003011431/ 78 | _RETRIABLE_STATUS_CODES = { 79 | requests.codes.internal_server_error, 80 | requests.codes.bad_gateway, 81 | requests.codes.service_unavailable, 82 | *list(range(520, 530)), 83 | } 84 | # pylint: enable=no-member 85 | 86 | 87 | def _is_retriable(method, code): 88 | return code in _RETRIABLE_STATUS_CODES or ( 89 | method == "GET" and code in _RETRIABLE_FOR_GETS 90 | ) 91 | 92 | 93 | class IonQAPIError(IonQError): 94 | """Base exception for fatal API errors. 95 | 96 | Attributes: 97 | status_code(int): An HTTP response status code. 98 | error_type(str): An error type string from the IonQ REST API. 99 | """ 100 | 101 | def __init__(self, message, status_code, headers, body, error_type): # pylint: disable=too-many-positional-arguments 102 | super().__init__(message) 103 | self.status_code = status_code 104 | self.headers = headers 105 | self.body = body 106 | self.error_type = error_type 107 | 108 | @classmethod 109 | def raise_for_status(cls, response) -> IonQAPIError | None: 110 | """Raise an instance of the exception class from an API response object if needed. 111 | Args: 112 | response (:class:`Response `): An IonQ REST API response. 113 | 114 | Raises: 115 | IonQAPIError: instance of `cls` with error detail from `response`. 116 | IonQRetriableError: instance of `cls` with error detail from `response`.""" 117 | status_code = response.status_code 118 | if status_code == 200: 119 | return None 120 | res = cls.from_response(response) 121 | if _is_retriable(response.request.method, status_code): 122 | raise IonQRetriableError(res) 123 | raise res 124 | 125 | @classmethod 126 | def from_response(cls, response: requests.Response) -> IonQAPIError: 127 | """Raise an instance of the exception class from an API response object. 128 | 129 | Args: 130 | response (:class:`Response `): An IonQ REST API response. 131 | 132 | Returns: 133 | IonQAPIError: instance of `cls` with error detail from `response`. 134 | 135 | """ 136 | # TODO: Pending API changes will cleanup this error logic: 137 | status_code = response.status_code 138 | headers = response.headers 139 | body = response.text 140 | try: 141 | response_json = response.json() 142 | except jd.JSONDecodeError: 143 | response_json = {"invalid_json": response.text} 144 | # Defaults, if items cannot be extracted from the response. 145 | error_type = "internal_error" 146 | message = "No error details provided." 147 | if "code" in response_json: 148 | # { "code": , "message": } 149 | message = response_json.get("message") or message 150 | elif "statusCode" in response_json: 151 | # { "statusCode": , "error": , "message": } 152 | message = response_json.get("message") or message 153 | error_type = response_json.get("error") or error_type 154 | elif "error" in response_json: 155 | # { "error": { "type": , "message: } } 156 | error_data = response_json.get("error") 157 | message = error_data.get("message") or message 158 | error_type = error_data.get("type") or error_type 159 | return cls(message, status_code, headers, body, error_type) 160 | 161 | def __str__(self): 162 | return ( 163 | f"{self.__class__.__name__}(" 164 | f"message={self.message!r}," 165 | f"status_code={self.status_code!r}," 166 | f"headers={self.headers}," 167 | f"body={self.body}," 168 | f"error_type={self.error_type!r})" 169 | ) 170 | 171 | def __reduce__(self): 172 | return ( 173 | self.__class__, 174 | (self.message, self.status_code, self.headers, self.body, self.error_type), 175 | ) 176 | 177 | 178 | class IonQBackendError(IonQError): 179 | """Errors generated from improper usage of IonQBackend objects.""" 180 | 181 | 182 | class IonQJobError(IonQError, JobError): 183 | """Errors generated from improper usage of IonQJob objects.""" 184 | 185 | 186 | class IonQJobFailureError(IonQError, JobError): 187 | """Errors generated from jobs that fail on the API side.""" 188 | 189 | 190 | class IonQJobStateError(IonQError, JobError): 191 | """Errors generated from attempting to do something to a job that its state would not allow""" 192 | 193 | 194 | class IonQGateError(IonQError, JobError): 195 | """Errors generated from invalid gate defs 196 | 197 | Attributes: 198 | gate_name: The name of the gate which caused this error. 199 | """ 200 | 201 | def __init__(self, gate_name: str, gateset: Literal["qis", "native"]): 202 | self.gate_name = gate_name 203 | self.gateset = gateset 204 | super().__init__( 205 | ( 206 | f"gate '{gate_name}' is not supported on the '{gateset}' IonQ backends. " 207 | "Please use the qiskit.transpile method, manually rewrite to remove the gate, " 208 | "or change the gateset selection as appropriate." 209 | ) 210 | ) 211 | 212 | def __repr__(self): 213 | return f"{self.__class__.__name__}(gate_name={self.gate_name!r}, gateset={self.gateset!r})" 214 | 215 | 216 | class IonQMidCircuitMeasurementError(IonQError, JobError): 217 | """Errors generated from attempting mid-circuit measurement, which is not supported. 218 | Measurement must come after all instructions. 219 | 220 | Attributes: 221 | qubit_index: The qubit index to be measured mid-circuit 222 | """ 223 | 224 | def __init__(self, qubit_index: int, gate_name: str): 225 | self.qubit_index = qubit_index 226 | self.gate_name = gate_name 227 | super().__init__( 228 | f"Attempting to put '{gate_name}' after a measurement on qubit {qubit_index}. " 229 | "Mid-circuit measurement is not supported." 230 | ) 231 | 232 | def __str__(self): 233 | kwargs = f"qubit_index={self.qubit_index!r}, gate_name={self.gate_name!r}" 234 | return f"{self.__class__.__name__}({kwargs})" 235 | 236 | 237 | class IonQJobTimeoutError(IonQError, JobTimeoutError): 238 | """Errors generated from job timeouts""" 239 | 240 | 241 | class IonQPauliExponentialError(IonQError): 242 | """Errors generated from improper usage of Pauli exponentials.""" 243 | 244 | 245 | __all__ = [ 246 | "IonQError", 247 | "IonQCredentialsError", 248 | "IonQClientError", 249 | "IonQAPIError", 250 | "IonQBackendError", 251 | "IonQJobError", 252 | "IonQGateError", 253 | "IonQMidCircuitMeasurementError", 254 | "IonQJobTimeoutError", 255 | ] 256 | -------------------------------------------------------------------------------- /qiskit_ionq/ionq_client.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Basic API Client for IonQ's REST API""" 28 | 29 | from __future__ import annotations 30 | 31 | import json 32 | from collections import OrderedDict 33 | from typing import Optional, TYPE_CHECKING 34 | from warnings import warn 35 | import requests 36 | 37 | from . import exceptions 38 | from .helpers import qiskit_to_ionq, get_user_agent, retry 39 | from .exceptions import IonQRetriableError 40 | 41 | if TYPE_CHECKING: 42 | from .ionq_job import IonQJob 43 | 44 | 45 | class IonQClient: 46 | """IonQ API Client 47 | 48 | Attributes: 49 | _url(str): A URL base to use for API calls, e.g. ``"https://api.ionq.co/v0.3"`` 50 | _token(str): An API Access Token to use with the IonQ API. 51 | _custom_headers(dict): Extra headers to add to the request. 52 | """ 53 | 54 | def __init__( 55 | self, 56 | token: Optional[str] = None, 57 | url: Optional[str] = None, 58 | custom_headers: Optional[dict] = None, 59 | ): 60 | self._token = token 61 | self._custom_headers = custom_headers or {} 62 | # strip trailing slashes from our base URL. 63 | if url and url.endswith("/"): 64 | url = url[:-1] 65 | self._url = url 66 | self._user_agent = get_user_agent() 67 | 68 | @property 69 | def api_headers(self) -> dict: 70 | """API Headers needed to make calls to the REST API. 71 | 72 | Returns: 73 | dict[str, str]: A dict of :class:`requests.Request` headers. 74 | """ 75 | return { 76 | **self._custom_headers, 77 | "Authorization": f"apiKey {self._token}", 78 | "Content-Type": "application/json", 79 | "User-Agent": self._user_agent, 80 | } 81 | 82 | def make_path(self, *parts: str) -> str: 83 | """Make a "/"-delimited path, then append it to :attr:`_url`. 84 | 85 | Returns: 86 | str: A URL to use for an API call. 87 | """ 88 | return f"{self._url}/{'/'.join(parts)}" 89 | 90 | def _get_with_retry(self, req_path, params=None, headers=None, timeout=30): 91 | """Make a GET request with retry logic and exception handling. 92 | 93 | Args: 94 | req_path (str): The URL path to make the request to. 95 | params (dict, optional): Parameters to include in the request. 96 | headers (dict, optional): Headers to include in the request. 97 | timeout (int, optional): Timeout for the request. 98 | 99 | Raises: 100 | IonQRetriableError: When a retriable error occurs during the request. 101 | 102 | Returns: 103 | Response: A requests.Response object. 104 | """ 105 | try: 106 | res = requests.get( 107 | req_path, 108 | params=params, 109 | headers=headers, 110 | timeout=timeout, 111 | ) 112 | except requests.exceptions.RequestException as req_exc: 113 | raise IonQRetriableError(req_exc) from req_exc 114 | 115 | return res 116 | 117 | @retry(exceptions=IonQRetriableError, tries=5) 118 | def submit_job(self, job: IonQJob) -> dict: 119 | """Submit job to IonQ API 120 | 121 | This returns a JSON dict with status "submitted" and the job's id. 122 | 123 | Args: 124 | job (IonQJob): The IonQ Job instance to submit to the API. 125 | 126 | Raises: 127 | IonQAPIError: When the API returns a non-200 status code. 128 | 129 | Returns: 130 | dict: A :mod:`requests ` response :meth:`json ` dict. 131 | """ 132 | as_json = qiskit_to_ionq( 133 | job.circuit, 134 | job.backend(), 135 | job._passed_args, 136 | job.extra_query_params, 137 | job.extra_metadata, 138 | ) 139 | req_path = self.make_path("jobs") 140 | res = requests.post( 141 | req_path, 142 | data=as_json, 143 | headers=self.api_headers, 144 | timeout=30, 145 | ) 146 | exceptions.IonQAPIError.raise_for_status(res) 147 | return res.json() 148 | 149 | @retry(exceptions=IonQRetriableError, max_delay=60, backoff=2, jitter=1) 150 | def retrieve_job(self, job_id: str) -> dict: 151 | """Retrieve job information from the IonQ API. 152 | 153 | The returned JSON dict will only have data if job has completed. 154 | 155 | Args: 156 | job_id (str): The ID of a job to retrieve. 157 | 158 | Raises: 159 | IonQAPIError: When the API returns a non-200 status code. 160 | IonQRetriableError: When a retriable error occurs during the request. 161 | 162 | Returns: 163 | dict: A :mod:`requests ` response :meth:`json ` dict. 164 | """ 165 | req_path = self.make_path("jobs", job_id) 166 | res = self._get_with_retry(req_path, headers=self.api_headers) 167 | exceptions.IonQAPIError.raise_for_status(res) 168 | return res.json() 169 | 170 | @retry(exceptions=IonQRetriableError, tries=5) 171 | def cancel_job(self, job_id: str) -> dict: 172 | """Attempt to cancel a job which has not yet run. 173 | 174 | .. NOTE:: If the job has already reached status "completed", this cancel action is a no-op. 175 | 176 | Args: 177 | job_id (str): The ID of the job to cancel. 178 | 179 | Raises: 180 | IonQAPIError: When the API returns a non-200 status code. 181 | 182 | Returns: 183 | dict: A :mod:`requests ` response :meth:`json ` dict. 184 | """ 185 | req_path = self.make_path("jobs", job_id, "status", "cancel") 186 | res = requests.put(req_path, headers=self.api_headers, timeout=30) 187 | exceptions.IonQAPIError.raise_for_status(res) 188 | return res.json() 189 | 190 | def cancel_jobs(self, job_ids: list[str]) -> list[dict]: 191 | """Cancel multiple jobs at once. 192 | 193 | Args: 194 | job_ids (list): A list of job IDs to cancel. 195 | 196 | Returns: 197 | list: A list of :meth:`cancel_job ` responses. 198 | """ 199 | return [self.cancel_job(job_id) for job_id in job_ids] 200 | 201 | @retry(exceptions=IonQRetriableError, tries=3) 202 | def delete_job(self, job_id: str) -> dict: 203 | """Delete a job and associated data. 204 | 205 | Args: 206 | job_id (str): The ID of the job to delete. 207 | 208 | Raises: 209 | IonQAPIError: When the API returns a non-200 status code. 210 | 211 | Returns: 212 | dict: A :mod:`requests ` response :meth:`json ` dict. 213 | """ 214 | req_path = self.make_path("jobs", job_id) 215 | res = requests.delete(req_path, headers=self.api_headers, timeout=30) 216 | exceptions.IonQAPIError.raise_for_status(res) 217 | return res.json() 218 | 219 | @retry(exceptions=IonQRetriableError, max_delay=60, backoff=2, jitter=1) 220 | def get_calibration_data(self, backend_name: str) -> dict: 221 | """Retrieve calibration data for a specified backend. 222 | 223 | Args: 224 | backend_name (str): The IonQ backend to fetch data for. 225 | 226 | Raises: 227 | IonQAPIError: When the API returns a non-200 status code. 228 | IonQRetriableError: When a retriable error occurs during the request. 229 | 230 | Returns: 231 | dict: A dictionary of an IonQ backend's calibration data. 232 | """ 233 | req_path = self.make_path( 234 | "/".join(["characterizations/backends", backend_name[5:], "current"]) 235 | ) 236 | res = self._get_with_retry(req_path, headers=self.api_headers) 237 | exceptions.IonQAPIError.raise_for_status(res) 238 | return res.json() 239 | 240 | @retry(exceptions=IonQRetriableError, max_delay=60, backoff=2, jitter=1) 241 | def get_results( 242 | self, 243 | job_id: str, 244 | sharpen: Optional[bool] = None, 245 | extra_query_params: Optional[dict] = None, 246 | ) -> dict: 247 | """Retrieve job results from the IonQ API. 248 | 249 | The returned JSON dict will only have data if job has completed. 250 | 251 | Args: 252 | job_id (str): The ID of a job to retrieve. 253 | sharpen (bool): Supported if the job is debiased, 254 | allows you to filter out physical qubit bias from the results. 255 | extra_query_params (dict): Specify any parameters to include in the request 256 | 257 | Raises: 258 | IonQAPIError: When the API returns a non-200 status code. 259 | IonQRetriableError: When a retriable error occurs during the request. 260 | 261 | Returns: 262 | dict: A :mod:`requests ` response :meth:`json ` dict. 263 | """ 264 | 265 | params = {} 266 | 267 | if sharpen is not None: 268 | params["sharpen"] = sharpen 269 | 270 | if extra_query_params is not None: 271 | warn( 272 | ( 273 | f"The parameter(s): {extra_query_params} is not checked by default " 274 | "but will be submitted in the request." 275 | ) 276 | ) 277 | params.update(extra_query_params) 278 | 279 | req_path = self.make_path("jobs", job_id, "results") 280 | res = self._get_with_retry(req_path, headers=self.api_headers, params=params) 281 | exceptions.IonQAPIError.raise_for_status(res) 282 | # Use json.loads with object_pairs_hook to maintain order of JSON keys 283 | return json.loads(res.text, object_pairs_hook=OrderedDict) 284 | 285 | 286 | __all__ = ["IonQClient"] 287 | -------------------------------------------------------------------------------- /qiskit_ionq/ionq_equivalence_library.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2024 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | 28 | """Equivalences for IonQ native gates.""" 29 | 30 | import numpy as np 31 | from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary 32 | from qiskit.circuit import QuantumRegister, QuantumCircuit, Parameter 33 | from qiskit.circuit.library import CXGate, RXGate, RZGate, UGate, XGate, CU3Gate 34 | from .ionq_gates import GPIGate, GPI2Gate, MSGate 35 | 36 | 37 | def u_gate_equivalence() -> None: 38 | """Add U gate equivalence to the SessionEquivalenceLibrary.""" 39 | q = QuantumRegister(1, "q") 40 | theta_param = Parameter("theta_param") 41 | phi_param = Parameter("phi_param") 42 | lambda_param = Parameter("lambda_param") 43 | u_gate = QuantumCircuit(q) 44 | # this sequence can be compacted if virtual-z gates will be introduced 45 | u_gate.append(GPI2Gate(0.5 - lambda_param / (2 * np.pi)), [0]) 46 | u_gate.append( 47 | GPIGate( 48 | theta_param / (4 * np.pi) 49 | + phi_param / (4 * np.pi) 50 | - lambda_param / (4 * np.pi) 51 | ), 52 | [0], 53 | ) 54 | u_gate.append(GPI2Gate(0.5 + phi_param / (2 * np.pi)), [0]) 55 | SessionEquivalenceLibrary.add_equivalence( 56 | UGate(theta_param, phi_param, lambda_param), u_gate 57 | ) 58 | 59 | 60 | def cx_gate_equivalence() -> None: 61 | """Add CX gate equivalence to the SessionEquivalenceLibrary.""" 62 | q = QuantumRegister(2, "q") 63 | cx_gate = QuantumCircuit(q) 64 | cx_gate.append(GPI2Gate(1 / 4), [0]) 65 | cx_gate.append(MSGate(0, 0), [0, 1]) 66 | cx_gate.append(GPI2Gate(1 / 2), [0]) 67 | cx_gate.append(GPI2Gate(1 / 2), [1]) 68 | cx_gate.append(GPI2Gate(-1 / 4), [0]) 69 | SessionEquivalenceLibrary.add_equivalence(CXGate(), cx_gate) 70 | 71 | 72 | # Below are the rules needed for Aer simulator to simulate circuits containing IonQ native gates 73 | 74 | 75 | def gpi_gate_equivalence() -> None: 76 | """Add GPI gate equivalence to the SessionEquivalenceLibrary.""" 77 | q = QuantumRegister(1, "q") 78 | phi_param = Parameter("phi_param") 79 | gpi_gate = QuantumCircuit(q) 80 | gpi_gate.append(XGate(), [0]) 81 | gpi_gate.append(RZGate(4 * phi_param * np.pi), [0]) 82 | SessionEquivalenceLibrary.add_equivalence(GPIGate(phi_param), gpi_gate) 83 | 84 | 85 | def gpi2_gate_equivalence() -> None: 86 | """Add GPI2 gate equivalence to the SessionEquivalenceLibrary.""" 87 | q = QuantumRegister(1, "q") 88 | phi_param = Parameter("phi_param") 89 | gpi2_gate = QuantumCircuit(q) 90 | gpi2_gate.append(RZGate(-2 * phi_param * np.pi), [0]) 91 | gpi2_gate.append(RXGate(np.pi / 2), [0]) 92 | gpi2_gate.append(RZGate(2 * phi_param * np.pi), [0]) 93 | SessionEquivalenceLibrary.add_equivalence(GPI2Gate(phi_param), gpi2_gate) 94 | 95 | 96 | def ms_gate_equivalence() -> None: 97 | """Add MS gate equivalence to the SessionEquivalenceLibrary.""" 98 | q = QuantumRegister(2, "q") 99 | phi0_param = Parameter("phi0_param") 100 | phi1_param = Parameter("phi1_param") 101 | theta_param = Parameter("theta_param") 102 | alpha_param = phi0_param + phi1_param 103 | beta_param = phi0_param - phi1_param 104 | ms_gate = QuantumCircuit(q) 105 | ms_gate.append(CXGate(), [1, 0]) 106 | ms_gate.x(0) 107 | ms_gate.append( 108 | CU3Gate( 109 | 2 * theta_param * np.pi, 110 | 2 * alpha_param * np.pi - np.pi / 2, 111 | np.pi / 2 - 2 * alpha_param * np.pi, 112 | ), 113 | [0, 1], 114 | ) 115 | ms_gate.x(0) 116 | ms_gate.append( 117 | CU3Gate( 118 | 2 * theta_param * np.pi, 119 | -2 * beta_param * np.pi - np.pi / 2, 120 | np.pi / 2 + 2 * beta_param * np.pi, 121 | ), 122 | [0, 1], 123 | ) 124 | ms_gate.append(CXGate(), [1, 0]) 125 | SessionEquivalenceLibrary.add_equivalence( 126 | MSGate(phi0_param, phi1_param, theta_param), ms_gate 127 | ) 128 | 129 | 130 | def add_equivalences() -> None: 131 | """Add IonQ gate equivalences to the SessionEquivalenceLibrary.""" 132 | u_gate_equivalence() 133 | cx_gate_equivalence() 134 | gpi_gate_equivalence() 135 | gpi2_gate_equivalence() 136 | ms_gate_equivalence() 137 | -------------------------------------------------------------------------------- /qiskit_ionq/ionq_gates.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2021 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Native gateset for IonQ hardware.""" 28 | 29 | from typing import Optional 30 | import math 31 | import numpy as np 32 | from qiskit.circuit.gate import Gate 33 | from qiskit.circuit.parameterexpression import ParameterValueType 34 | 35 | 36 | class GPIGate(Gate): 37 | r"""Single-qubit GPI gate. 38 | **Circuit symbol:** 39 | .. parsed-literal:: 40 | ┌───────┐ 41 | q_0: ┤ GPI(φ)├ 42 | └───────┘ 43 | **Matrix Representation:** 44 | 45 | .. math:: 46 | 47 | GPI(\phi) = 48 | \begin{pmatrix} 49 | 0 & e^{-i*2*\pi*\phi} \\ 50 | e^{i*2*\pi*\phi} & 0 51 | \end{pmatrix} 52 | """ 53 | 54 | def __init__(self, phi: ParameterValueType, label: Optional[str] = None): 55 | """Create new GPI gate.""" 56 | super().__init__("gpi", 1, [phi], label=label) 57 | 58 | def __array__(self, dtype=None): 59 | """Return a numpy array for the GPI gate.""" 60 | top = np.exp(-1j * 2 * math.pi * self.params[0]) 61 | bottom = np.exp(1j * 2 * math.pi * self.params[0]) 62 | return np.array([[0, top], [bottom, 0]], dtype=dtype) 63 | 64 | 65 | class GPI2Gate(Gate): 66 | r"""Single-qubit GPI2 gate. 67 | **Circuit symbol:** 68 | .. parsed-literal:: 69 | ┌───────┐ 70 | q_0: ┤GPI2(φ)├ 71 | └───────┘ 72 | **Matrix Representation:** 73 | 74 | .. math:: 75 | 76 | GPI2(\phi) = 77 | \frac{1}{\sqrt{2}} 78 | \begin{pmatrix} 79 | 1 & -i*e^{-i*2*\pi*\phi} \\ 80 | -i*e^{i*2*\pi*\phi} & 1 81 | \end{pmatrix} 82 | """ 83 | 84 | def __init__(self, phi: ParameterValueType, label: Optional[str] = None): 85 | """Create new GPI2 gate.""" 86 | super().__init__("gpi2", 1, [phi], label=label) 87 | 88 | def __array__(self, dtype=None): 89 | """Return a numpy array for the GPI2 gate.""" 90 | top = -1j * np.exp(-1j * self.params[0] * 2 * math.pi) 91 | bottom = -1j * np.exp(1j * self.params[0] * 2 * math.pi) 92 | return 1 / np.sqrt(2) * np.array([[1, top], [bottom, 1]], dtype=dtype) 93 | 94 | 95 | class MSGate(Gate): 96 | r"""Entangling 2-Qubit MS gate. 97 | **Circuit symbol:** 98 | .. parsed-literal:: 99 | _______ 100 | q_0: ┤ ├- 101 | |MS(ϴ,0)| 102 | q_1: ┤ ├- 103 | └───────┘ 104 | **Matrix representation:** 105 | 106 | .. math:: 107 | 108 | MS(\phi_0, \phi_1, \theta) = 109 | \begin{pmatrix} 110 | cos(\theta*\pi) & 0 & 0 & -i*e^{-i*2*\pi(\phi_0+\phi_1)}*sin(\theta*\pi) \\ 111 | 0 & cos(\theta*\pi) & -i*e^{i*2*\pi(\phi_0-\phi_1)}*sin(\theta*\pi) & 0 \\ 112 | 0 & -i*e^{-i*2*\pi(\phi_0-\phi_1)}*sin(\theta*\pi) & cos(\theta*\pi) & 0 \\ 113 | -i*e^{i*2*\pi(\phi_0+\phi_1)}*sin(\theta*\pi) & 0 & 0 & cos(\theta*\pi) 114 | \end{pmatrix} 115 | """ 116 | 117 | def __init__( 118 | self, 119 | phi0: ParameterValueType, 120 | phi1: ParameterValueType, 121 | theta: Optional[ParameterValueType] = 0.25, 122 | label: Optional[str] = None, 123 | ): 124 | """Create new MS gate.""" 125 | super().__init__( 126 | "ms", 127 | 2, 128 | [phi0, phi1, theta], 129 | label=label, 130 | ) 131 | 132 | def __array__(self, dtype=None): 133 | """Return a numpy array for the MS gate.""" 134 | phi0 = self.params[0] 135 | phi1 = self.params[1] 136 | theta = self.params[2] 137 | diag = np.cos(math.pi * theta) 138 | sin = np.sin(math.pi * theta) 139 | 140 | return np.array( 141 | [ 142 | [diag, 0, 0, sin * -1j * np.exp(-1j * 2 * math.pi * (phi0 + phi1))], 143 | [0, diag, sin * -1j * np.exp(1j * 2 * math.pi * (phi0 - phi1)), 0], 144 | [0, sin * -1j * np.exp(-1j * 2 * math.pi * (phi0 - phi1)), diag, 0], 145 | [sin * -1j * np.exp(1j * 2 * math.pi * (phi0 + phi1)), 0, 0, diag], 146 | ], 147 | dtype=dtype, 148 | ) 149 | 150 | 151 | class ZZGate(Gate): 152 | r"""Two-qubit ZZ-rotation gate. 153 | **Circuit Symbol:** 154 | .. parsed-literal:: 155 | q_0: ───■──── 156 | │zz(θ) 157 | q_1: ───■──── 158 | **Matrix Representation:** 159 | 160 | .. math:: 161 | 162 | ZZ(\theta) = 163 | \begin{pmatrix} 164 | e^{-i \theta*\pi} & 0 & 0 & 0 \\ 165 | 0 & e^{i \theta*\pi} & 0 & 0 \\ 166 | 0 & 0 & e^{i \theta*\pi} & 0 \\ 167 | 0 & 0 & 0 & e^{-i \theta\*\pi} 168 | \end{pmatrix} 169 | """ 170 | 171 | def __init__(self, theta: ParameterValueType, label: Optional[str] = None): 172 | """Create new ZZ gate.""" 173 | super().__init__("zz", 2, [theta], label=label) 174 | 175 | def __array__(self, dtype=None) -> np.ndarray: 176 | """Return a numpy array for the ZZ gate.""" 177 | itheta2 = 1j * float(self.params[0]) * math.pi 178 | return np.array( 179 | [ 180 | [np.exp(-itheta2), 0, 0, 0], 181 | [0, np.exp(itheta2), 0, 0], 182 | [0, 0, np.exp(itheta2), 0], 183 | [0, 0, 0, np.exp(-itheta2)], 184 | ], 185 | dtype=dtype, 186 | ) 187 | -------------------------------------------------------------------------------- /qiskit_ionq/ionq_optimizer_plugin.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2024 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Optimize the transpiling of IonQ native gates using a custom 28 | pass manager and a pass manager plugin TrappedIonOptimizerPlugin 29 | which consolidates all the rewrite rules in one single plugin. 30 | The other plugin classes are intended for testing various rewrite 31 | rules in isolation. 32 | """ 33 | 34 | from qiskit.transpiler import PassManager 35 | from qiskit.transpiler.preset_passmanagers.plugin import PassManagerStagePlugin 36 | from qiskit.transpiler.passmanager_config import PassManagerConfig 37 | from qiskit.converters import circuit_to_dag 38 | from qiskit_ionq.rewrite_rules import ( 39 | CancelGPI2Adjoint, 40 | CancelGPIAdjoint, 41 | GPI2TwiceIsGPI, 42 | CompactMoreThanThreeSingleQubitGates, 43 | CommuteGPIsThroughMS, 44 | ) 45 | 46 | 47 | class CustomPassManager(PassManager): 48 | """A custom pass manager.""" 49 | 50 | # pylint: disable=arguments-differ 51 | def run(self, circuit): 52 | """ 53 | Runs the pass manager iteratively until no further optimizations are possible. 54 | """ 55 | optimized_circuit = super().run(circuit) 56 | 57 | while True: 58 | previous_dag = circuit_to_dag(optimized_circuit) 59 | optimized_circuit = super().run(optimized_circuit) 60 | current_dag = circuit_to_dag(optimized_circuit) 61 | 62 | if current_dag == previous_dag: 63 | break 64 | 65 | return optimized_circuit 66 | 67 | 68 | class TrappedIonOptimizerPluginSimpleRules(PassManagerStagePlugin): 69 | """ 70 | This class is no intended to be used in production, is is meant 71 | to test the following transformation passes in isolation: 72 | - CancelGPI2Adjoint 73 | - CancelGPIAdjoint 74 | - GPI2TwiceIsGPI 75 | """ 76 | 77 | def pass_manager( 78 | self, 79 | pass_manager_config: PassManagerConfig = None, 80 | optimization_level: int = 0, 81 | ) -> PassManager: # pylint: disable=unused-argument 82 | """ 83 | Creates a PassManager class with added custom transformation passes. 84 | """ 85 | custom_pass_manager = CustomPassManager() 86 | if optimization_level == 0: 87 | pass 88 | if optimization_level >= 1: 89 | custom_pass_manager.append(CancelGPI2Adjoint()) 90 | custom_pass_manager.append(CancelGPIAdjoint()) 91 | custom_pass_manager.append(GPI2TwiceIsGPI()) 92 | return custom_pass_manager 93 | 94 | 95 | class TrappedIonOptimizerPluginCompactGates(PassManagerStagePlugin): 96 | """ 97 | This class is no intended to be used in production, is is meant 98 | to test the following transformation passes in isolation: 99 | - CompactMoreThanThreeSingleQubitGates 100 | """ 101 | 102 | def pass_manager( 103 | self, 104 | pass_manager_config: PassManagerConfig = None, 105 | optimization_level: int = 0, 106 | ) -> PassManager: # pylint: disable=unused-argument 107 | """ 108 | Creates a PassManager class with added custom transformation passes. 109 | """ 110 | custom_pass_manager = CustomPassManager() 111 | if optimization_level == 0: 112 | pass 113 | if optimization_level >= 1: 114 | custom_pass_manager.append(CompactMoreThanThreeSingleQubitGates()) 115 | return custom_pass_manager 116 | 117 | 118 | class TrappedIonOptimizerPluginCommuteGpi2ThroughMs(PassManagerStagePlugin): 119 | """ 120 | This class is no intended to be used in production, is is meant 121 | to test the following transformation passes in isolation: 122 | - CommuteGPIsThroughMS 123 | """ 124 | 125 | def pass_manager( 126 | self, 127 | pass_manager_config: PassManagerConfig = None, 128 | optimization_level: int = 0, 129 | ) -> PassManager: # pylint: disable=unused-argument 130 | """ 131 | Creates a PassManager class with added custom transformation passes. 132 | This class is meant to be used in production. 133 | """ 134 | custom_pass_manager = CustomPassManager() 135 | if optimization_level == 0: 136 | pass 137 | if optimization_level >= 1: 138 | custom_pass_manager.append(CommuteGPIsThroughMS()) 139 | return custom_pass_manager 140 | 141 | 142 | class TrappedIonOptimizerPlugin(PassManagerStagePlugin): 143 | """ 144 | This the optimizer plugin is intended to be used in production. 145 | """ 146 | 147 | def pass_manager( 148 | self, 149 | pass_manager_config: PassManagerConfig = None, 150 | optimization_level: int = 0, 151 | ) -> PassManager: # pylint: disable=unused-argument 152 | """ 153 | Creates a PassManager class with added custom transformation passes. 154 | This class is meant to be used in production. 155 | """ 156 | custom_pass_manager = CustomPassManager() 157 | if optimization_level == 0: 158 | pass 159 | if optimization_level >= 1: 160 | # Note that the TransformationPasses will be applied 161 | # in the order they have been added to the pass manager 162 | custom_pass_manager.append(CancelGPI2Adjoint()) 163 | custom_pass_manager.append(CancelGPIAdjoint()) 164 | custom_pass_manager.append(GPI2TwiceIsGPI()) 165 | custom_pass_manager.append(CommuteGPIsThroughMS()) 166 | custom_pass_manager.append(CompactMoreThanThreeSingleQubitGates()) 167 | return custom_pass_manager 168 | -------------------------------------------------------------------------------- /qiskit_ionq/ionq_provider.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Provider for interacting with IonQ backends""" 28 | 29 | from __future__ import annotations 30 | 31 | import logging 32 | 33 | from typing import Callable, Literal, Optional 34 | 35 | from qiskit.providers.exceptions import QiskitBackendNotFoundError 36 | from qiskit.providers.providerutils import filter_backends 37 | from .helpers import resolve_credentials 38 | 39 | from . import ionq_backend 40 | 41 | 42 | logger = logging.getLogger(__name__) 43 | 44 | 45 | class IonQProvider: 46 | """Provider for interacting with IonQ backends 47 | 48 | Attributes: 49 | credentials(dict[str, str]): A dictionary containing ``token`` and 50 | ``url`` keys, whose values are an IonQ API Access Token and 51 | IonQ API URL, respectively. 52 | """ 53 | 54 | name = "ionq_provider" 55 | 56 | def __init__( 57 | self, 58 | token: Optional[str] = None, 59 | url: Optional[str] = None, 60 | custom_headers: Optional[dict] = None, 61 | ): 62 | super().__init__() 63 | self.custom_headers = custom_headers 64 | self.credentials = resolve_credentials(token, url) 65 | self.backends = BackendService( 66 | [ 67 | ionq_backend.IonQSimulatorBackend(self), 68 | ionq_backend.IonQQPUBackend(self), 69 | ] 70 | ) 71 | 72 | def get_backend( 73 | self, 74 | name: str, 75 | gateset: Literal["qis", "native"] = "qis", 76 | **kwargs, 77 | ) -> ionq_backend.Backend: 78 | """Return a single backend matching the specified filtering. 79 | Args: 80 | name (str): name of the backend. 81 | gateset (str): language used (QIS or native), defaults to QIS. 82 | **kwargs: dict used for filtering. 83 | Returns: 84 | Backend: a backend matching the filtering. 85 | Raises: 86 | QiskitBackendNotFoundError: if no backend could be found or 87 | more than one backend matches the filtering criteria. 88 | """ 89 | name = "ionq_" + name if not name.startswith("ionq_") else name 90 | backends = self.backends(name, **kwargs) 91 | if len(backends) > 1: 92 | raise QiskitBackendNotFoundError("More than one backend matches criteria.") 93 | if not backends: 94 | raise QiskitBackendNotFoundError("No backend matches criteria.") 95 | 96 | return backends[0].with_name(name, gateset=gateset) 97 | 98 | 99 | class BackendService: 100 | """A service class that allows for autocompletion 101 | of backends from provider. 102 | """ 103 | 104 | def __init__(self, backends: list[ionq_backend.Backend]): 105 | """Initialize service 106 | 107 | Parameters: 108 | backends (list): List of backend instances. 109 | """ 110 | self._backends = backends 111 | for backend in backends: 112 | setattr(self, backend.name(), backend) 113 | 114 | def __call__( 115 | self, name: Optional[str] = None, filters: Optional[Callable] = None, **kwargs 116 | ) -> list[ionq_backend.Backend]: 117 | """A listing of all backends from this provider. 118 | 119 | Parameters: 120 | name (str): The name of a given backend. 121 | filters (callable): A filter function. 122 | kwargs (dict): A dictionary of other keyword arguments. 123 | 124 | Returns: 125 | list: A list of backends, if any. 126 | 127 | Example: 128 | 129 | ..jupyter-execute:: 130 | 131 | from qiskit_ionq import IonQProvider 132 | ionq = IonQProvider('TOKEN') 133 | sim = ionq.backends(filters=lambda x: x.configuration().simulator) 134 | print(sim) 135 | 136 | """ 137 | # pylint: disable=arguments-differ 138 | backends = self._backends 139 | if name: 140 | backends = [b for b in self._backends if name.startswith(b.name())] 141 | return filter_backends(backends, filters, **kwargs) 142 | -------------------------------------------------------------------------------- /qiskit_ionq/ionq_result.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """ 28 | IonQ result implementation that extends to allow for retrieval of probabilities. 29 | """ 30 | 31 | from qiskit.exceptions import QiskitError 32 | from qiskit.result import Result 33 | from qiskit.result.counts import Counts 34 | from . import exceptions 35 | 36 | 37 | class IonQResult(Result): 38 | """ 39 | An IonQ-specific result object. 40 | 41 | The primary reason this class extends the base Qiskit result object is to 42 | provide an API for retrieving result probabilities directly. 43 | """ 44 | 45 | def get_probabilities(self, experiment=None): 46 | """ 47 | Get probabilities for the experiment. 48 | 49 | If ``experiment`` is None, ``self.results`` will be used in its place. 50 | 51 | Args: 52 | experiment (Union[int, QuantumCircuit, Schedule, dict], optional): If provided, this 53 | argument is used to get an experiment using Result's ``_get_experiment`` method. 54 | 55 | Raises: 56 | IonQJobError: A given experiment in our results had no probabilities. 57 | 58 | Returns: 59 | Union[Counts, list(Counts)]: Single count if the result list 60 | was size one, else the entire list. 61 | """ 62 | if experiment is None: 63 | exp_keys = range(len(self.results)) 64 | else: 65 | exp_keys = [experiment] 66 | 67 | dict_list = [] 68 | for key in exp_keys: 69 | exp = self._get_experiment(key) 70 | try: 71 | header = exp.header.to_dict() 72 | except (AttributeError, QiskitError): # header is not available 73 | header = None 74 | 75 | if "probabilities" in self.data(key).keys(): 76 | counts_header = {} 77 | if header: 78 | counts_header = { 79 | k: v 80 | for k, v in header.items() 81 | if k in {"time_taken", "creg_sizes", "memory_slots"} 82 | } 83 | dict_list.append( 84 | Counts(self.data(key)["probabilities"], **counts_header) 85 | ) 86 | else: 87 | raise exceptions.IonQJobError( 88 | f'No probabilities for experiment "{repr(key)}"' 89 | ) 90 | 91 | # Return first item of dict_list if size is 1 92 | if len(dict_list) == 1: 93 | return dict_list[0] 94 | return dict_list 95 | -------------------------------------------------------------------------------- /qiskit_ionq/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiskit-community/qiskit-ionq/313daf7ea7ad02c826db5fd53466d2cac569e08a/qiskit_ionq/py.typed -------------------------------------------------------------------------------- /qiskit_ionq/rewrite_rules.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2024 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Rewrite rules for optimizing the transpilatiom to IonQ native gates.""" 28 | 29 | import math 30 | from sympy import Matrix, exp, sqrt 31 | 32 | from qiskit import QuantumCircuit 33 | from qiskit.converters import circuit_to_dag 34 | from qiskit.dagcircuit import DAGCircuit 35 | from qiskit.dagcircuit.dagnode import DAGOpNode 36 | from qiskit.quantum_info import Operator 37 | from qiskit.synthesis import OneQubitEulerDecomposer 38 | from qiskit.transpiler.basepasses import TransformationPass 39 | 40 | from .ionq_gates import GPIGate, GPI2Gate 41 | 42 | 43 | class CancelGPI2Adjoint(TransformationPass): 44 | """GPI2 times GPI2 adjoint should cancel.""" 45 | 46 | def run(self, dag: DAGCircuit) -> DAGCircuit: 47 | nodes_to_remove = [] 48 | 49 | for node in dag.topological_op_nodes(): 50 | if ( 51 | isinstance(node, DAGOpNode) 52 | and node.op.name == "gpi2" 53 | and node not in nodes_to_remove 54 | ): 55 | successors = [ 56 | succ 57 | for succ in dag.quantum_successors(node) 58 | if isinstance(succ, DAGOpNode) 59 | ] 60 | for next_node in successors: 61 | if next_node.op.name == "gpi2" and node.qargs == next_node.qargs: 62 | phi1 = node.op.params[0] 63 | phi2 = next_node.op.params[0] 64 | if math.isclose((phi2 + 0.5) % 1, phi1 % 1) or math.isclose( 65 | (phi1 + 0.5) % 1, phi2 % 1 66 | ): 67 | nodes_to_remove.extend([node, next_node]) 68 | 69 | for node in nodes_to_remove: 70 | dag.remove_op_node(node) 71 | 72 | return dag 73 | 74 | 75 | class CancelGPIAdjoint(TransformationPass): 76 | """GPI times GPI should cancel.""" 77 | 78 | def run(self, dag: DAGCircuit) -> DAGCircuit: 79 | nodes_to_remove = [] 80 | 81 | for node in dag.topological_op_nodes(): 82 | if node.op.name == "gpi" and node not in nodes_to_remove: 83 | successors = [ 84 | succ 85 | for succ in dag.quantum_successors(node) 86 | if isinstance(succ, DAGOpNode) 87 | ] 88 | for next_node in successors: 89 | if next_node.op.name == "gpi" and node.qargs == next_node.qargs: 90 | phi1 = node.op.params[0] 91 | phi2 = next_node.op.params[0] 92 | if math.isclose(phi1, phi2): 93 | nodes_to_remove.extend([node, next_node]) 94 | 95 | for node in nodes_to_remove: 96 | dag.remove_op_node(node) 97 | 98 | return dag 99 | 100 | 101 | class GPI2TwiceIsGPI(TransformationPass): 102 | """GPI2 times GPI2 is GPI times -i. Below the -i factor will be ignored.""" 103 | 104 | def run(self, dag: DAGCircuit) -> DAGCircuit: 105 | nodes_to_remove = [] 106 | 107 | for node in dag.topological_op_nodes(): 108 | if node.op.name == "gpi2" and node not in nodes_to_remove: 109 | successors = [ 110 | succ 111 | for succ in dag.quantum_successors(node) 112 | if isinstance(succ, DAGOpNode) 113 | ] 114 | for next_node in successors: 115 | if next_node.op.name == "gpi2" and node.qargs == next_node.qargs: 116 | phi1 = node.op.params[0] 117 | phi2 = next_node.op.params[0] 118 | if math.isclose(phi1, phi2): 119 | qc = QuantumCircuit(dag.num_qubits()) 120 | qubit_index = dag.qubits.index(node.qargs[0]) 121 | qc.append(GPIGate(phi1), [qubit_index]) 122 | qc_dag = circuit_to_dag(qc) 123 | 124 | wire_mapping = {qarg: qarg for qarg in next_node.qargs} 125 | 126 | dag.substitute_node_with_dag( 127 | next_node, qc_dag, wires=wire_mapping 128 | ) 129 | nodes_to_remove.append(node) 130 | 131 | for node in nodes_to_remove: 132 | dag.remove_op_node(node) 133 | 134 | return dag 135 | 136 | 137 | class CompactMoreThanThreeSingleQubitGates(TransformationPass): 138 | """More than three single qubit gates in series are collapsed to 3 gates.""" 139 | 140 | def run(self, dag: DAGCircuit) -> DAGCircuit: 141 | nodes_to_remove = [] 142 | visited_nodes = [] 143 | 144 | for node in dag.topological_op_nodes(): 145 | if (node.op.name in ("gpi", "gpi2")) and node not in visited_nodes: 146 | single_qubit_gates_streak = self.get_streak_recursively(dag, [node]) 147 | if len(single_qubit_gates_streak) > 3: 148 | self.compact_single_qubits_streak(dag, single_qubit_gates_streak) 149 | nodes_to_remove.extend(single_qubit_gates_streak[:-1]) 150 | visited_nodes.extend(single_qubit_gates_streak[:-1]) 151 | else: 152 | visited_nodes.extend(single_qubit_gates_streak) 153 | 154 | for node in nodes_to_remove: 155 | dag.remove_op_node(node) 156 | 157 | return dag 158 | 159 | def get_streak_recursively(self, dag, streak): 160 | """Recursively build up single qubit streak.""" 161 | last_node = streak[-1] 162 | successors = [ 163 | succ 164 | for succ in dag.quantum_successors(last_node) 165 | if isinstance(succ, DAGOpNode) 166 | ] 167 | for node in successors: 168 | if (node.op.name in ("gpi", "gpi2")) and last_node.qargs == node.qargs: 169 | streak.append(node) 170 | return self.get_streak_recursively(dag, streak) 171 | return streak 172 | 173 | def multiply_node_matrices(self, nodes: list) -> Matrix: 174 | """Compute matrix multiplication.""" 175 | matrix = Matrix([[1, 0], [0, 1]]) 176 | for node in nodes: 177 | if node.op.name == "gpi": 178 | phi = node.op.params[0] 179 | matrix = ( 180 | Matrix( 181 | [ 182 | [0, exp(-1j * phi * 2 * math.pi)], 183 | [exp(1j * phi * 2 * math.pi), 0], 184 | ] 185 | ) 186 | * matrix 187 | ) 188 | if node.op.name == "gpi2": 189 | phi = node.op.params[0] 190 | matrix = ( 191 | Matrix( 192 | [ 193 | [1, -1j * exp(-1j * phi * 2 * math.pi)], 194 | [-1j * exp(1j * phi * 2 * math.pi), 1], 195 | ] 196 | ) 197 | * matrix 198 | * (1 / sqrt(2)) 199 | ) 200 | return matrix 201 | 202 | def get_euler_angles(self, matrix: Matrix) -> tuple: 203 | """Get Euler angles decomposition for an arbitrary matrix.""" 204 | operator = Operator(matrix.tolist()) 205 | decomposer = OneQubitEulerDecomposer("U3") 206 | theta, phi, lambd = decomposer.angles(operator) 207 | return (theta, phi, lambd) 208 | 209 | def compact_single_qubits_streak(self, dag, single_qubit_gates_streak): 210 | """Merge GPIs gates in a single qubit streak to 3 GPIs gates.""" 211 | matrix = self.multiply_node_matrices(single_qubit_gates_streak) 212 | theta, phi, lambd = self.get_euler_angles(matrix) 213 | last_gate = single_qubit_gates_streak[-1] 214 | 215 | qc = QuantumCircuit(dag.num_qubits()) 216 | qubit_index = dag.qubits.index(last_gate.qargs[0]) 217 | qc.append(GPI2Gate(0.5 - lambd / (2 * math.pi)), [qubit_index]) 218 | qc.append( 219 | GPIGate( 220 | theta / (4 * math.pi) + phi / (4 * math.pi) - lambd / (4 * math.pi) 221 | ), 222 | [qubit_index], 223 | ) 224 | qc.append(GPI2Gate(0.5 + phi / (2 * math.pi)), [qubit_index]) 225 | qc_dag = circuit_to_dag(qc) 226 | 227 | wire_mapping = {qarg: qarg for qarg in last_gate.qargs} 228 | dag.substitute_node_with_dag(last_gate, qc_dag, wires=wire_mapping) 229 | 230 | 231 | class CommuteGPIsThroughMS(TransformationPass): 232 | """GPI(0), GPI(π), GPI(-π), GPI2(0), GPI2(π), GPI2(-π) 233 | on either qubit commute with MS""" 234 | 235 | def run(self, dag: DAGCircuit) -> DAGCircuit: 236 | nodes_to_remove = set() 237 | 238 | for node in dag.topological_op_nodes(): 239 | if node in nodes_to_remove: 240 | continue 241 | 242 | if (node.op.name in ("gpi", "gpi2")) and ( 243 | math.isclose(node.op.params[0], 0) 244 | or math.isclose(node.op.params[0], 0.5) 245 | or math.isclose(node.op.params[0], -0.5) 246 | ): 247 | successors = [ 248 | succ for succ in dag.successors(node) if isinstance(succ, DAGOpNode) 249 | ] 250 | for next_node in successors: 251 | if ( 252 | next_node.op.name == "ms" 253 | and math.isclose(next_node.op.params[0], 0) 254 | and math.isclose(next_node.op.params[1], 0) 255 | and math.isclose(next_node.op.params[2], 0.25) 256 | and node.qargs[0] in next_node.qargs 257 | ): 258 | sub_dag = DAGCircuit() 259 | for qreg in dag.qregs.values(): 260 | sub_dag.add_qreg(qreg) 261 | 262 | # map the ops to the qubits in the sub-DAG 263 | ms_qubits = [next_node.qargs[0], next_node.qargs[1]] 264 | gpis_qubit = [node.qargs[0]] 265 | 266 | sub_dag.apply_operation_back(next_node.op, ms_qubits) 267 | sub_dag.apply_operation_back(node.op, gpis_qubit) 268 | 269 | wire_mapping = {qubit: qubit for qubit in ms_qubits} 270 | wire_mapping[node.qargs[0]] = node.qargs[0] 271 | 272 | dag.substitute_node_with_dag( 273 | next_node, sub_dag, wires=wire_mapping 274 | ) 275 | nodes_to_remove.add(node) 276 | break 277 | 278 | for node in nodes_to_remove: 279 | dag.remove_op_node(node) 280 | 281 | return dag 282 | -------------------------------------------------------------------------------- /qiskit_ionq/version.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Contains the package version.""" 28 | 29 | import os 30 | import pathlib 31 | import subprocess 32 | from typing import List 33 | 34 | pkg_parent = pathlib.Path(__file__).parent.parent.absolute() 35 | 36 | # major, minor, patch 37 | VERSION_INFO = ".".join(map(str, (0, 5, 13))) 38 | 39 | 40 | def _minimal_ext_cmd(cmd: List[str]) -> bytes: 41 | # construct minimal environment 42 | env = { 43 | "LANGUAGE": "C", # LANGUAGE is used on win32 44 | "LANG": "C", 45 | "LC_ALL": "C", 46 | } 47 | for k in ["SYSTEMROOT", "PATH"]: 48 | v = os.environ.get(k) 49 | if v is not None: 50 | env[k] = v 51 | proc = subprocess.Popen( 52 | cmd, 53 | stdout=subprocess.PIPE, 54 | stderr=subprocess.PIPE, 55 | env=env, 56 | cwd=str(pkg_parent), 57 | ) 58 | out = proc.communicate()[0] 59 | if proc.returncode > 0: 60 | raise OSError 61 | return out 62 | 63 | 64 | def git_version() -> str: 65 | """Get the current git head sha1.""" 66 | # Determine if we're at master 67 | try: 68 | out = _minimal_ext_cmd(["git", "rev-parse", "HEAD"]) 69 | git_revision = out.strip().decode("ascii") 70 | except OSError: 71 | git_revision = "Unknown" 72 | 73 | return git_revision 74 | 75 | 76 | def get_version_info() -> str: 77 | """Get the full version string.""" 78 | # Adding the git rev number needs to be done inside 79 | # write_version_py(), otherwise the import of scipy.version messes 80 | # up the build under Python 3. 81 | git_dir = pkg_parent / ".git" 82 | if not git_dir.exists(): 83 | return VERSION_INFO 84 | 85 | full_version = VERSION_INFO 86 | try: 87 | release = _minimal_ext_cmd(["git", "tag", "-l", "--points-at", "HEAD"]) 88 | except Exception: # pylint: disable=broad-except 89 | return full_version 90 | 91 | if not release: 92 | git_revision = git_version() 93 | if ".dev" not in full_version: 94 | full_version += ".dev0" 95 | full_version += "+" + git_revision[:7] 96 | 97 | return full_version 98 | 99 | 100 | __version__ = get_version_info() 101 | 102 | __all__ = ["__version__"] 103 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | qiskit<2.0.0 2 | Sphinx>=4.5.0 3 | sphinx-tabs>=1.1.11 4 | sphinx-autodoc-typehints 5 | jupyter-sphinx 6 | qiskit-sphinx-theme~=1.16.0 7 | sphinx-panels 8 | ipykernel 9 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | qiskit<2.0.0 2 | pytest 3 | requests-mock>=1.8.0 4 | pytest-cov==2.10.1 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | decorator>=5.1.0 2 | requests>=2.24.0 3 | importlib-metadata>=4.11.4 4 | python-dotenv>=1.0.1 5 | qiskit<2.0.0 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test = pytest 3 | 4 | [tool:pytest] 5 | addopts = -vvv --cov=qiskit_ionq --cov-report=html --cov-branch 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This code is part of Qiskit. 3 | # 4 | # (C) Copyright IBM 2017, 2018. 5 | # 6 | # This code is licensed under the Apache License, Version 2.0. You may 7 | # obtain a copy of this license in the LICENSE.txt file in the root directory 8 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 9 | # 10 | # Any modifications or derivative works of this code must retain this 11 | # copyright notice, and modified files need to carry a notice indicating 12 | # that they have been altered from the originals. 13 | 14 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 15 | # 16 | # Licensed under the Apache License, Version 2.0 (the "License"); 17 | # you may not use this file except in compliance with the License. 18 | # You may obtain a copy of the License at 19 | # 20 | # http://www.apache.org/licenses/LICENSE-2.0 21 | # 22 | # Unless required by applicable law or agreed to in writing, software 23 | # distributed under the License is distributed on an "AS IS" BASIS, 24 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | # See the License for the specific language governing permissions and 26 | # limitations under the License. 27 | 28 | """setup module for packaging and distribution""" 29 | 30 | import os 31 | 32 | from setuptools import find_packages, setup 33 | 34 | here = os.path.dirname(os.path.realpath(__file__)) 35 | readme_path = os.path.join(here, "README.md") 36 | requirements_path = os.path.join(here, "requirements.txt") 37 | test_requirements_path = os.path.join(here, "requirements-test.txt") 38 | version_path = os.path.join(here, "qiskit_ionq", "version.py") 39 | 40 | with open(readme_path, "r") as _fp: 41 | long_description = _fp.read() 42 | 43 | with open(requirements_path) as _fp: 44 | REQUIREMENTS = _fp.readlines() 45 | 46 | with open(test_requirements_path) as _fp: 47 | TEST_REQUIREMENTS = _fp.readlines() 48 | 49 | # This is needed to prevent importing any package specific dependencies at 50 | # stages of the setup.py life-cycle where they may not yet be installed. 51 | __version__ = None 52 | with open(version_path) as _fp: 53 | exec(_fp.read()) # pylint: disable=exec-used 54 | 55 | setup( 56 | name="qiskit-ionq", 57 | version=__version__, 58 | author="IonQ", 59 | author_email="info@ionq.com", 60 | packages=find_packages(exclude=["test*"]), 61 | description="Qiskit provider for IonQ backends", 62 | long_description=long_description, 63 | long_description_content_type="text/markdown", 64 | url="https://github.com/qiskit-partners/qiskit-ionq", 65 | license="Apache 2.0", 66 | classifiers=[ 67 | "Environment :: Console", 68 | "License :: OSI Approved :: Apache Software License", 69 | "Intended Audience :: Developers", 70 | "Intended Audience :: Science/Research", 71 | "Operating System :: Microsoft :: Windows", 72 | "Operating System :: MacOS", 73 | "Operating System :: POSIX :: Linux", 74 | "Programming Language :: Python :: 3 :: Only", 75 | "Programming Language :: Python :: 3.9", 76 | "Programming Language :: Python :: 3.10", 77 | "Programming Language :: Python :: 3.11", 78 | "Programming Language :: Python :: 3.12", 79 | "Programming Language :: Python :: 3.13", 80 | "Topic :: Scientific/Engineering", 81 | ], 82 | keywords="qiskit sdk quantum", 83 | python_requires=">=3.9", 84 | setup_requires=[], 85 | install_requires=REQUIREMENTS, 86 | extras_require={"test": TEST_REQUIREMENTS}, 87 | zip_safe=False, 88 | include_package_data=True, 89 | package_data={"qiskit_ionq": ["py.typed"]}, 90 | project_urls={ 91 | "Bug Tracker": "https://github.com/qiskit-partners/qiskit-ionq/issues", 92 | "Source Code": "https://github.com/qiskit-partners/qiskit-ionq", 93 | }, 94 | ) 95 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """global pytest fixtures""" 28 | 29 | import pytest 30 | import requests_mock as _requests_mock 31 | from qiskit.providers.models.backendconfiguration import BackendConfiguration 32 | from requests_mock import adapter as rm_adapter 33 | 34 | from qiskit_ionq import ionq_backend, ionq_job, ionq_provider 35 | from qiskit_ionq.helpers import compress_to_metadata_string 36 | 37 | 38 | class MockBackend(ionq_backend.IonQBackend): 39 | """A mock backend for testing super-class behavior in isolation.""" 40 | 41 | def gateset(self): 42 | return "qis" 43 | 44 | def __init__(self, provider, name="ionq_mock_backend"): # pylint: disable=redefined-outer-name 45 | config = BackendConfiguration.from_dict( 46 | { 47 | "backend_name": name, 48 | "backend_version": "0.0.1", 49 | "simulator": True, 50 | "local": True, 51 | "coupling_map": None, 52 | "description": "IonQ Mock Backend", 53 | "n_qubits": 29, 54 | "conditional": False, 55 | "open_pulse": False, 56 | "memory": False, 57 | "max_shots": 0, 58 | "basis_gates": [], 59 | "gates": [ 60 | { 61 | "name": "TODO", 62 | "parameters": [], 63 | "qasm_def": "TODO", 64 | } 65 | ], 66 | } 67 | ) 68 | super().__init__(config, provider=provider) 69 | 70 | def with_name(self, name, **kwargs): 71 | """Helper method that returns this backend with a more specific target system.""" 72 | return MockBackend(self._provider, name, **kwargs) 73 | 74 | 75 | def dummy_job_response( 76 | job_id, target="mock_backend", status="completed", job_settings=None, children=None 77 | ): 78 | """A dummy response payload for `job_id`. 79 | 80 | Args: 81 | job_id (str): An arbitrary job id. 82 | target (str): Backend target string. 83 | status (str): A provided status string. 84 | job_settings (dict): Settings provided to the API. 85 | children (list): A list of child job IDs. 86 | 87 | Returns: 88 | dict: A json response dict. 89 | """ 90 | qiskit_header = compress_to_metadata_string( 91 | { 92 | "qubit_labels": [["q", 0], ["q", 1]], 93 | "n_qubits": 2, 94 | "qreg_sizes": [["q", 2]], 95 | "clbit_labels": [["c", 0], ["c", 1]], 96 | "memory_slots": 2, 97 | "creg_sizes": [["c", 2]], 98 | "name": job_id, 99 | "global_phase": 0, 100 | } 101 | ) 102 | response = { 103 | "status": status, 104 | "predicted_execution_time": 4, 105 | "metadata": { 106 | "qobj_id": "test_qobj_id", 107 | "shots": "1234", 108 | "sampler_seed": "42", 109 | "output_length": "2", 110 | "qiskit_header": qiskit_header, 111 | }, 112 | "registers": {"meas_mapped": [0, 1]}, 113 | "execution_time": 8, 114 | "qubits": 2, 115 | "type": "circuit", 116 | "request": 1600000000, 117 | "start": 1600000001, 118 | "response": 1600000002, 119 | "target": target, 120 | "id": job_id, 121 | "settings": (job_settings or {}), 122 | "name": "test_name", 123 | } 124 | 125 | if children is not None: 126 | response["children"] = children 127 | 128 | return response 129 | 130 | 131 | def dummy_mapped_job_response( 132 | job_id, target="mock_backend", status="completed", job_settings=None, children=None 133 | ): 134 | """A dummy mapped response payload for `job_id`. 135 | 136 | Args: 137 | job_id (str): An arbitrary job id. 138 | target (str): Backend target string. 139 | status (str): A provided status string. 140 | job_settings (dict): Settings provided to the API. 141 | children (list): A list of child job IDs. 142 | 143 | Returns: 144 | dict: A json response dict. 145 | """ 146 | qiskit_header = compress_to_metadata_string( 147 | { 148 | "qubit_labels": [["q", 0], ["q", 1]], 149 | "n_qubits": 2, 150 | "qreg_sizes": [["q", 2]], 151 | "clbit_labels": [["c", 0], ["c", 1]], 152 | "memory_slots": 2, 153 | "creg_sizes": [["c", 2]], 154 | "name": job_id, 155 | "global_phase": 0, 156 | } 157 | ) 158 | response = { 159 | "status": status, 160 | "predicted_execution_time": 4, 161 | "metadata": { 162 | "qobj_id": "test_qobj_id", 163 | "shots": "1234", 164 | "sampler_seed": "42", 165 | "output_length": "2", 166 | "qiskit_header": qiskit_header, 167 | }, 168 | "registers": {"meas_mapped": [1, 0]}, 169 | "execution_time": 8, 170 | "qubits": 2, 171 | "type": "circuit", 172 | "request": 1600000000, 173 | "start": 1600000001, 174 | "response": 1600000002, 175 | "target": target, 176 | "id": job_id, 177 | "settings": (job_settings or {}), 178 | "name": "test_name", 179 | } 180 | 181 | if children is not None: 182 | response["children"] = children 183 | 184 | return response 185 | 186 | 187 | def dummy_failed_job(job_id): # pylint: disable=differing-param-doc,differing-type-doc 188 | """A dummy response payload for a failed job. 189 | 190 | Args: 191 | job_id (str): An arbitrary job id. 192 | status (str): A provided status string. 193 | 194 | Returns: 195 | dict: A json response dict. 196 | 197 | """ 198 | qiskit_header = compress_to_metadata_string( 199 | { 200 | "qubit_labels": [["q", 0], ["q", 1]], 201 | "n_qubits": 2, 202 | "qreg_sizes": [["q", 2]], 203 | "clbit_labels": [["c", 0], ["c", 1]], 204 | "memory_slots": 2, 205 | "creg_sizes": [["c", 2]], 206 | "name": job_id, 207 | "global_phase": 0, 208 | } 209 | ) 210 | return { 211 | "failure": {"error": "example error", "code": "ExampleError"}, 212 | "status": "failed", 213 | "metadata": {"shots": "1", "qiskit_header": qiskit_header}, 214 | "type": "circuit", 215 | "request": 1600000000, 216 | "response": 1600000002, 217 | "target": "qpu", 218 | "id": job_id, 219 | } 220 | 221 | 222 | def _default_requests_mock(**kwargs): 223 | """Create a default `requests_mock.Mocker` for use in tests. 224 | 225 | Args: 226 | kwargs (dict): Any additional kwargs to create the mocker with. 227 | 228 | Returns: 229 | :class:`request_mock.Mocker`: A requests mocker. 230 | """ 231 | mocker_kwargs = {"real_http": False, **kwargs} 232 | mocker = _requests_mock.Mocker(**mocker_kwargs) 233 | return mocker 234 | 235 | 236 | def pytest_sessionstart(session): 237 | """pytest hook for global test session start 238 | 239 | Args: 240 | session (:class:`pytest.Session`): A pytest session object. 241 | """ 242 | session.global_requests_mock = _default_requests_mock() 243 | session.global_requests_mock.start() 244 | session.global_requests_mock.register_uri( 245 | rm_adapter.ANY, 246 | rm_adapter.ANY, 247 | status_code=599, 248 | text="UNHANDLED REQUEST. PLEASE MOCK WITH requests_mock.", 249 | ) 250 | 251 | 252 | def pytest_sessionfinish(session): 253 | """pytest hook for global test session end 254 | 255 | Args: 256 | session (:class:`pytest.Session`): A pytest session object. 257 | """ 258 | session.global_requests_mock.stop() 259 | del session.global_requests_mock 260 | 261 | 262 | @pytest.fixture() 263 | def provider(): 264 | """Fixture for injecting a test provider. 265 | 266 | Returns: 267 | IonQProvider: A provider suitable for testing. 268 | """ 269 | return ionq_provider.IonQProvider("token") 270 | 271 | 272 | @pytest.fixture() 273 | def mock_backend(provider): # pylint: disable=redefined-outer-name 274 | """A fixture instance of the :class:`MockBackend`. 275 | 276 | Args: 277 | provider (IonQProvider): An IonQProvider fixture. 278 | 279 | Returns: 280 | MockBackenbd: An instance of :class:`MockBackend` 281 | """ 282 | return MockBackend(provider) 283 | 284 | 285 | # pylint: disable=redefined-outer-name 286 | @pytest.fixture() 287 | def qpu_backend(provider): 288 | """Get the QPU backend from a provider. 289 | 290 | Args: 291 | provider (IonQProvider): Injected provider from :meth:`provider`. 292 | 293 | Returns: 294 | IonQQPUBackend: An instance of an IonQQPUBackend. 295 | """ 296 | return provider.get_backend("ionq_qpu") 297 | 298 | 299 | # pylint: disable=redefined-outer-name 300 | @pytest.fixture() 301 | def simulator_backend(provider): 302 | """Get the QPU backend from a provider. 303 | 304 | Args: 305 | provider (IonQProvider): Injected provider from :meth:`provider`. 306 | 307 | Returns: 308 | IonQQPUBackend: An instance of an IonQQPUBackend. 309 | """ 310 | return provider.get_backend("ionq_simulator") 311 | 312 | 313 | # pylint: disable=redefined-outer-name 314 | @pytest.fixture() 315 | def formatted_result(provider): 316 | """Fixture for auto-injecting a formatted IonQJob result object into a 317 | a sub-class of ``unittest.TestCase``. 318 | 319 | Args: 320 | provider (IonQProvider): Injected provider from :meth:`provider`. 321 | 322 | Returns: 323 | Result: A qiskit result from making a fake API call with StubbedClient. 324 | """ 325 | # Dummy job ID for formatted results fixture. 326 | job_id = "test_id" 327 | settings = {"lorem": {"ipsum": "dolor"}} 328 | 329 | # Create a backend and client to use for accessing the job. 330 | backend = provider.get_backend("ionq_qpu.aria-1") 331 | backend.set_options(job_settings=settings) 332 | client = backend.create_client() 333 | 334 | # Create the request path for accessing the dummy job: 335 | path = client.make_path("jobs", job_id) 336 | results_path = client.make_path("jobs", job_id, "results") 337 | 338 | # mock a job response 339 | with _default_requests_mock() as requests_mock: 340 | # Mock the response with our dummy job response. 341 | requests_mock.get( 342 | path, json=dummy_job_response(job_id, "qpu.aria-1", "completed", settings) 343 | ) 344 | 345 | requests_mock.get(results_path, json={"0": 0.5, "2": 0.499999}) 346 | 347 | # Create the job (this calls self.status(), which will fetch the job). 348 | job = ionq_job.IonQJob(backend, job_id, client) 349 | 350 | # Yield so that the mock context manager properly unwinds. 351 | yield job.result() 352 | -------------------------------------------------------------------------------- /test/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | -------------------------------------------------------------------------------- /test/helpers/test_gate_serialization.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Test the qobj_to_ionq function.""" 28 | 29 | import pytest 30 | import numpy as np 31 | from qiskit.circuit import ( 32 | QuantumCircuit, 33 | QuantumRegister, 34 | ClassicalRegister, 35 | instruction, 36 | Parameter, 37 | ) 38 | from qiskit.circuit.library import PauliEvolutionGate 39 | from qiskit.quantum_info import SparsePauliOp 40 | 41 | from qiskit_ionq import exceptions 42 | from qiskit_ionq.helpers import qiskit_circ_to_ionq_circ 43 | 44 | compiler_directives = ["barrier"] 45 | unsupported_instructions = [ 46 | "reset", 47 | "initialize", 48 | "u", 49 | "custom-gate", 50 | "custom-gate-2", 51 | ] 52 | 53 | gate_serializations = [ 54 | ("ccx", [0, 1, 2], [{"gate": "x", "targets": [2], "controls": [0, 1]}]), 55 | ("ch", [0, 1], [{"gate": "h", "targets": [1], "controls": [0]}]), 56 | ("cx", [0, 1], [{"gate": "x", "targets": [1], "controls": [0]}]), 57 | ( 58 | "cp", 59 | [0.5, 0, 1], 60 | [{"gate": "z", "rotation": 0.5, "targets": [1], "controls": [0]}], 61 | ), 62 | ( 63 | "crx", 64 | [0, 0, 1], 65 | [{"gate": "rx", "rotation": 0, "targets": [1], "controls": [0]}], 66 | ), 67 | ( 68 | "crx", 69 | [0.5, 0, 1], 70 | [{"gate": "rx", "rotation": 0.5, "targets": [1], "controls": [0]}], 71 | ), 72 | ( 73 | "cry", 74 | [0, 0, 1], 75 | [{"gate": "ry", "rotation": 0, "targets": [1], "controls": [0]}], 76 | ), 77 | ( 78 | "cry", 79 | [0.5, 0, 1], 80 | [{"gate": "ry", "rotation": 0.5, "targets": [1], "controls": [0]}], 81 | ), 82 | ( 83 | "crz", 84 | [0, 0, 1], 85 | [{"gate": "rz", "rotation": 0, "targets": [1], "controls": [0]}], 86 | ), 87 | ( 88 | "crz", 89 | [0.5, 0, 1], 90 | [{"gate": "rz", "rotation": 0.5, "targets": [1], "controls": [0]}], 91 | ), 92 | ("csx", [0, 1], [{"gate": "v", "targets": [1], "controls": [0]}]), 93 | ("cx", [0, 1], [{"gate": "x", "targets": [1], "controls": [0]}]), 94 | ("cy", [0, 1], [{"gate": "y", "targets": [1], "controls": [0]}]), 95 | ("cz", [0, 1], [{"gate": "z", "targets": [1], "controls": [0]}]), 96 | ("h", [0], [{"gate": "h", "targets": [0]}]), 97 | ("id", [0], []), 98 | ( 99 | "mcp", 100 | [0.5, [0, 1], 2], 101 | [{"gate": "z", "rotation": 0.5, "targets": [2], "controls": [0, 1]}], 102 | ), 103 | ("mcx", [[0, 1], 2], [{"gate": "x", "targets": [2], "controls": [0, 1]}]), 104 | # make sure that multi-control can take any number of controls 105 | ("mcx", [[0, 1, 2], 3], [{"gate": "x", "targets": [3], "controls": [0, 1, 2]}]), 106 | ( 107 | "mcx", 108 | [[0, 1, 2, 3], 4], 109 | [{"gate": "x", "targets": [4], "controls": [0, 1, 2, 3]}], 110 | ), 111 | ( 112 | "mcx", 113 | [[0, 1, 2, 3, 4], 5], 114 | [{"gate": "x", "targets": [5], "controls": [0, 1, 2, 3, 4]}], 115 | ), 116 | ("measure", [0, 0], []), 117 | ("p", [0, 0], [{"gate": "z", "rotation": 0, "targets": [0]}]), 118 | ("p", [0.5, 0], [{"gate": "z", "rotation": 0.5, "targets": [0]}]), 119 | ("rx", [0, 0], [{"gate": "rx", "rotation": 0, "targets": [0]}]), 120 | ("rx", [0.5, 0], [{"gate": "rx", "rotation": 0.5, "targets": [0]}]), 121 | ("rxx", [0, 0, 1], [{"gate": "xx", "rotation": 0, "targets": [0, 1]}]), 122 | ("rxx", [0.5, 0, 1], [{"gate": "xx", "rotation": 0.5, "targets": [0, 1]}]), 123 | ("ry", [0, 0], [{"gate": "ry", "rotation": 0, "targets": [0]}]), 124 | ("ry", [0.5, 0], [{"gate": "ry", "rotation": 0.5, "targets": [0]}]), 125 | ("ryy", [0, 0, 1], [{"gate": "yy", "rotation": 0, "targets": [0, 1]}]), 126 | ("ryy", [0.5, 0, 1], [{"gate": "yy", "rotation": 0.5, "targets": [0, 1]}]), 127 | ("rz", [0, 0], [{"gate": "rz", "rotation": 0, "targets": [0]}]), 128 | ("rz", [0.5, 0], [{"gate": "rz", "rotation": 0.5, "targets": [0]}]), 129 | ("s", [0], [{"gate": "s", "targets": [0]}]), 130 | ("sdg", [0], [{"gate": "si", "targets": [0]}]), 131 | ("swap", [0, 1], [{"gate": "swap", "targets": [0, 1]}]), 132 | ("sx", [0], [{"gate": "v", "targets": [0]}]), 133 | ("sxdg", [0], [{"gate": "vi", "targets": [0]}]), 134 | ("t", [0], [{"gate": "t", "targets": [0]}]), 135 | ("tdg", [0], [{"gate": "ti", "targets": [0]}]), 136 | ("x", [0], [{"gate": "x", "targets": [0]}]), 137 | ("y", [0], [{"gate": "y", "targets": [0]}]), 138 | ("z", [0], [{"gate": "z", "targets": [0]}]), 139 | ] 140 | 141 | 142 | @pytest.mark.parametrize("directive", compiler_directives) 143 | def test_compiler_directives(directive): 144 | """Test that compiler directives are skipped. 145 | 146 | Args: 147 | directive (str): A compiler directive name. 148 | """ 149 | unsupported = instruction.Instruction(directive, 0, 0, []) 150 | qc = QuantumCircuit(1, 1) 151 | qc.append(unsupported) 152 | circuit, _, _ = qiskit_circ_to_ionq_circ(qc) 153 | instruction_names = [instruction["gate"] for instruction in circuit] 154 | assert directive not in instruction_names 155 | 156 | 157 | @pytest.mark.parametrize("instruction_name", unsupported_instructions) 158 | def test_unsupported_instructions(instruction_name): 159 | """Test that trying to create a circuit that has an unsupported instruction 160 | results in an error. 161 | 162 | 163 | Args: 164 | instruction_name (str): an unsupported instruction name. 165 | """ 166 | unsupported = instruction.Instruction(instruction_name, 0, 0, []) 167 | qc = QuantumCircuit(1, 1) 168 | qc.append(unsupported) 169 | with pytest.raises(exceptions.IonQGateError) as exc: 170 | qiskit_circ_to_ionq_circ(qc) 171 | assert exc.value.gate_name == unsupported.name 172 | 173 | 174 | @pytest.mark.parametrize( 175 | "gate_name, gate_args, expected_serialization", gate_serializations 176 | ) 177 | def test_individual_instruction_serialization( 178 | gate_name, gate_args, expected_serialization 179 | ): # pylint: disable=invalid-name 180 | """Test that individual gates are correctly serialized 181 | 182 | Args: 183 | gate_name (str): the qiskit gate to test, as its QuantumCircuit method name 184 | gate_args (list): the arguments for the gate. Will have different 185 | entries and cardinality depending on the gate 186 | expected_serialization (list): expected serialization of the gate as a 187 | (normally single-element) list of instructions in IonQ API JSON format 188 | 189 | """ 190 | qc = QuantumCircuit(6, 6) 191 | getattr(qc, gate_name)(*gate_args) 192 | serialized, _, _ = qiskit_circ_to_ionq_circ(qc) 193 | assert serialized == expected_serialization 194 | 195 | 196 | def test_measurement_only_circuit(): 197 | """Test a valid circuit with only measurements.""" 198 | qc = QuantumCircuit(1, 1) 199 | qc.measure(0, 0) 200 | expected = [] 201 | built, _, _ = qiskit_circ_to_ionq_circ(qc) 202 | assert built == expected 203 | 204 | 205 | def test_simple_circuit(): 206 | """Test basic structure of a simple circuit""" 207 | qc = QuantumCircuit(1, 1) 208 | qc.h(0) 209 | qc.measure(0, 0) 210 | expected = [{"gate": "h", "targets": [0]}] 211 | built, _, _ = qiskit_circ_to_ionq_circ(qc) 212 | assert built == expected 213 | 214 | 215 | # pylint: disable=invalid-name 216 | def test_circuit_with_entangling_ops(): 217 | """Test structure of circuits with entangling ops.""" 218 | qc = QuantumCircuit(2, 2) 219 | qc.cx(1, 0) 220 | expected = [{"gate": "x", "targets": [0], "controls": [1]}] 221 | built, _, _ = qiskit_circ_to_ionq_circ(qc) 222 | assert built == expected 223 | 224 | 225 | def test_pauliexp_circuit(): 226 | """Test structure of circuits with a Pauli evolution gate.""" 227 | # build the evolution gate 228 | operator = SparsePauliOp(["XX", "YY", "ZZ"], coeffs=[0.1, 0.2, 0.3]) 229 | evo = PauliEvolutionGate(operator, time=0.4) 230 | # append it to a circuit 231 | circuit = QuantumCircuit(3) 232 | circuit.append(evo, [1, 2]) 233 | expected = [ 234 | { 235 | "gate": "pauliexp", 236 | "targets": [1, 2], 237 | "terms": ["XX", "YY", "ZZ"], 238 | "coefficients": [0.1, 0.2, 0.3], 239 | "time": 0.4, 240 | } 241 | ] 242 | built, _, _ = qiskit_circ_to_ionq_circ(circuit) 243 | assert built == expected 244 | 245 | 246 | @pytest.mark.parametrize("ionq_compiler_synthesis", [True, False]) 247 | def test_non_commuting_pauliexp_circuit(ionq_compiler_synthesis): 248 | """Test that non-commuting Pauli evolution gates raise an error.""" 249 | # build the evolution gate 250 | operator = SparsePauliOp(["XX", "XY"], coeffs=[0.1, 0.2]) 251 | evo = PauliEvolutionGate(operator, time=0.3) 252 | # append it to a circuit 253 | circuit = QuantumCircuit(2) 254 | circuit.append(evo, [0, 1]) 255 | if ionq_compiler_synthesis: 256 | qiskit_circ_to_ionq_circ( 257 | circuit, ionq_compiler_synthesis=ionq_compiler_synthesis 258 | ) 259 | else: 260 | with pytest.raises(exceptions.IonQPauliExponentialError) as _: 261 | qiskit_circ_to_ionq_circ( 262 | circuit, ionq_compiler_synthesis=ionq_compiler_synthesis 263 | ) 264 | 265 | 266 | def test_multi_control(): 267 | """Test structure of circuits with multiple controls""" 268 | qc = QuantumCircuit(3, 3) 269 | qc.ccx(0, 1, 2) 270 | expected = [{"gate": "x", "targets": [2], "controls": [0, 1]}] 271 | built, _, _ = qiskit_circ_to_ionq_circ(qc) 272 | assert built == expected 273 | 274 | 275 | def test_rotation_from_instruction_params(): 276 | """Test that instruction parameters are used for rotation.""" 277 | qc = QuantumCircuit(2, 2) 278 | qc.append(instruction.Instruction("rx", 2, 0, [1.0]), [1, 0]) 279 | built, _, _ = qiskit_circ_to_ionq_circ(qc) 280 | built = built[0] 281 | assert "rotation" in built 282 | assert built["rotation"] == 1.0 283 | 284 | 285 | def test_no_mid_circuit_measurement(): 286 | """ 287 | Test that putting an instruction on a qubit that has been measured is an 288 | invalid instruction 289 | """ 290 | qc = QuantumCircuit(2, 2) 291 | qc.measure(1, 1) 292 | qc.x(1) 293 | with pytest.raises(exceptions.IonQMidCircuitMeasurementError) as exc: 294 | qiskit_circ_to_ionq_circ(qc) 295 | assert exc.value.qubit_index == 1 296 | assert exc.value.gate_name == "x" 297 | 298 | 299 | def test_unordered_instructions_are_not_mid_circuit_measurement(): 300 | """Test that mid-circuit measurement is only an error if 301 | you try and put an instruction on a measured qubit.""" 302 | qc = QuantumCircuit(2, 2) 303 | qc.measure(1, 1) 304 | qc.x(0) 305 | expected = [{"gate": "x", "targets": [0]}] 306 | built, _, _ = qiskit_circ_to_ionq_circ(qc) 307 | assert built == expected 308 | 309 | 310 | def test_circuit_with_multiple_registers(): 311 | """Test that mid-circuit measurement is only an error if 312 | you try and put an instruction on a measured qubit.""" 313 | qr0 = QuantumRegister(2, "qr0") 314 | qr1 = QuantumRegister(2, "qr1") 315 | cr0 = ClassicalRegister(2, "cr0") 316 | cr1 = ClassicalRegister(2, "cr1") 317 | 318 | qc = QuantumCircuit( 319 | qr0, 320 | qr1, 321 | cr0, 322 | cr1, 323 | ) 324 | 325 | qc.x(qr0[0]) 326 | qc.h(qr0[1]) 327 | qc.y(qr1[0]) 328 | qc.z(qr1[1]) 329 | qc.cx(qr0[0], qr1[0]) 330 | qc.measure([qr0[0], qr0[1], qr1[0], qr1[1]], [cr0[0], cr0[1], cr1[0], cr1[1]]) 331 | 332 | expected = [ 333 | {"gate": "x", "targets": [0]}, 334 | {"gate": "h", "targets": [1]}, 335 | {"gate": "y", "targets": [2]}, 336 | {"gate": "z", "targets": [3]}, 337 | {"gate": "x", "controls": [0], "targets": [2]}, 338 | ] 339 | built, _, _ = qiskit_circ_to_ionq_circ(qc) 340 | assert built == expected 341 | 342 | 343 | def test_uncontrolled_multi_target_gates(): 344 | """Test that multi-target gates without controls are properly serialized.""" 345 | mtu = QuantumCircuit(2, name="rxx") 346 | mtu.h([0, 1]) 347 | mtu.cx(0, 1) 348 | mtu.rz(Parameter("theta"), 1) 349 | mtu.cx(0, 1) 350 | mtu.h([0, 1]) 351 | mtu_gate = mtu.to_gate(parameter_map={mtu.parameters[0]: np.pi / 2}) 352 | 353 | qc = QuantumCircuit(2) 354 | qc.append(mtu_gate, [0, 1]) 355 | 356 | expected = [ 357 | {"gate": "xx", "targets": [0, 1], "rotation": np.pi / 2}, 358 | ] 359 | built, _, _ = qiskit_circ_to_ionq_circ(qc) 360 | assert built == expected 361 | -------------------------------------------------------------------------------- /test/helpers/test_helpers.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Test the helper functions.""" 28 | 29 | import re 30 | from unittest.mock import patch, MagicMock 31 | from qiskit_ionq.ionq_client import IonQClient 32 | from qiskit_ionq.helpers import get_n_qubits, retry 33 | 34 | 35 | def test_user_agent_header(): 36 | """ 37 | Tests whether the generated user_agent contains all the required information with the right 38 | version format. 39 | """ 40 | ionq_client = IonQClient() 41 | generated_user_agent = ionq_client.api_headers["User-Agent"] 42 | 43 | user_agent_info_keywords = ["qiskit-ionq", "qiskit-terra", "os", "python"] 44 | # Checks if all keywords are present in user-agent string. 45 | all_user_agent_keywords_avail = all( 46 | keyword in generated_user_agent for keyword in user_agent_info_keywords 47 | ) 48 | 49 | # Checks whether there is at-least 3 version strings from qiskit-ionq, qiskit-terra, python. 50 | has_all_version_strings = len(re.findall(r"\s*([\d.]+)", generated_user_agent)) >= 3 51 | assert all_user_agent_keywords_avail and has_all_version_strings 52 | 53 | 54 | def test_get_n_qubits_success(): 55 | """Test get_n_qubits returns correct number of qubits and checks correct URL.""" 56 | with patch("requests.get") as mock_get: 57 | mock_response = MagicMock() 58 | mock_response.json.return_value = [ 59 | { 60 | "backend": "qpu.aria-1", 61 | "status": "unavailable", 62 | "qubits": 25, 63 | "average_queue_time": 722980302, 64 | "last_updated": 1729699872, 65 | "characterization_url": "/characterizations/9d699f61-d894-49b3-94c0-fd8173b32c27", 66 | "degraded": False, 67 | } 68 | ] 69 | mock_get.return_value = mock_response 70 | 71 | backend = "ionq_qpu.aria-1" 72 | result = get_n_qubits(backend) 73 | 74 | expected_url = "https://api.ionq.co/v0.3/backends" 75 | 76 | # Check the arguments of the last call to `requests.get` 77 | mock_get.assert_called() 78 | _, kwargs = mock_get.call_args 79 | assert ( 80 | kwargs["url"] == expected_url 81 | ), f"Expected URL {expected_url}, but got {kwargs['url']}" 82 | 83 | assert result == 25, f"Expected 25 qubits, but got {result}" 84 | 85 | 86 | def test_get_n_qubits_fallback(): 87 | """Test get_n_qubits returns fallback number of qubits and checks correct URL on failure.""" 88 | with patch("requests.get", side_effect=Exception("Network error")) as mock_get: 89 | backend = "aria-1" 90 | result = get_n_qubits(backend) 91 | 92 | expected_url = "https://api.ionq.co/v0.3/backends" 93 | 94 | # Check the arguments of the last call to `requests.get` 95 | mock_get.assert_called() 96 | _, kwargs = mock_get.call_args 97 | assert ( 98 | kwargs["url"] == expected_url 99 | ), f"Expected URL {expected_url}, but got {kwargs['url']}" 100 | 101 | assert result == 100, f"Expected fallback of 100 qubits, but got {result}" 102 | 103 | 104 | def test_retry(): 105 | """Test the retry decorator with both success and failure cases.""" 106 | # Test case where the function eventually succeeds 107 | attempt_success = {"count": 0} 108 | 109 | @retry(exceptions=ValueError, tries=3, delay=0) 110 | def func_success(): 111 | if attempt_success["count"] < 2: 112 | attempt_success["count"] += 1 113 | raise ValueError("Intentional Error") 114 | return "Success" 115 | 116 | result = func_success() 117 | assert ( 118 | attempt_success["count"] == 2 119 | ), f"Expected 2 retries, got {attempt_success['count']}" 120 | assert result == "Success", f"Expected 'Success', got {result}" 121 | 122 | # Test case where the function keeps failing and eventually raises the exception 123 | attempt_fail = {"count": 0} 124 | 125 | @retry(exceptions=ValueError, tries=3, delay=0) 126 | def func_fail(): 127 | attempt_fail["count"] += 1 128 | raise ValueError("Intentional Error") 129 | 130 | try: 131 | func_fail() 132 | except ValueError: 133 | pass 134 | else: 135 | assert False, "Expected ValueError was not raised" 136 | 137 | assert ( 138 | attempt_fail["count"] == 3 139 | ), f"Expected 3 retries, got {attempt_fail['count']}" 140 | 141 | # Test case where a different exception is raised and should not be retried 142 | attempt_wrong_exception = {"count": 0} 143 | 144 | @retry(exceptions=ValueError, tries=3, delay=0) 145 | def func_wrong_exception(): 146 | attempt_wrong_exception["count"] += 1 147 | raise TypeError("Wrong Exception Type") 148 | 149 | try: 150 | func_wrong_exception() 151 | except TypeError: 152 | pass 153 | else: 154 | assert False, "Expected TypeError was not raised" 155 | 156 | assert ( 157 | attempt_wrong_exception["count"] == 1 158 | ), f"Expected 1 attempt, got {attempt_wrong_exception['count']}" 159 | -------------------------------------------------------------------------------- /test/ionq_backend/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | -------------------------------------------------------------------------------- /test/ionq_backend/test_base_backend.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Tests for the IonQ's Backend base/super-class.""" 28 | # pylint: disable=redefined-outer-name 29 | 30 | from unittest import mock 31 | 32 | import pytest 33 | from qiskit import QuantumCircuit 34 | from qiskit.providers.models.backendstatus import BackendStatus 35 | 36 | from qiskit_ionq import exceptions, ionq_client, ionq_job 37 | 38 | from .. import conftest 39 | 40 | 41 | def test_status_dummy_response(mock_backend): 42 | """Test that the IonQBackend returns an aribtrary backend status. 43 | 44 | Args: 45 | mock_backend (MockBackend): A fake/mock IonQBackend. 46 | """ 47 | status = mock_backend.status() 48 | assert isinstance(status, BackendStatus) 49 | assert status.operational is True 50 | 51 | 52 | def test_client_property(mock_backend): 53 | """ 54 | Test that the client property is an IonQClient instance. 55 | 56 | Args: 57 | mock_backend (MockBackend): A fake/mock IonQBackend. 58 | """ 59 | # Client will be lazily created. 60 | client = mock_backend.client 61 | assert isinstance(client, ionq_client.IonQClient) 62 | 63 | 64 | def test_retrieve_job(mock_backend, requests_mock): 65 | """ 66 | Test that retrieve job returns a valid job instance. 67 | 68 | Args: 69 | mock_backend (MockBackend): A fake/mock IonQBackend. 70 | requests_mock (:class:`request_mock.Mocker`): A requests mocker. 71 | """ 72 | path = mock_backend.client.make_path("jobs", "fake_job_id") 73 | requests_mock.get(path, json=conftest.dummy_job_response("fake_job_id")) 74 | job = mock_backend.retrieve_job("fake_job_id") 75 | assert isinstance(job, ionq_job.IonQJob) 76 | assert job.job_id() == "fake_job_id" 77 | 78 | 79 | def test_retrieve_jobs(mock_backend, requests_mock): 80 | """ 81 | Test that retrieve job returns a valid job instance. 82 | 83 | Args: 84 | mock_backend (MockBackend): A fake/mock IonQBackend. 85 | requests_mock (:class:`request_mock.Mocker`): A requests mocker. 86 | """ 87 | job_ids = [f"fake_job_{i}" for i in range(10)] 88 | for job_id in job_ids: 89 | path = mock_backend.client.make_path("jobs", job_id) 90 | requests_mock.get(path, json=conftest.dummy_job_response(job_id)) 91 | jobs = mock_backend.retrieve_jobs(job_ids) 92 | 93 | # They're all jobs. 94 | assert all(isinstance(job, ionq_job.IonQJob) for job in jobs) 95 | 96 | # All IDs are accounted for 97 | assert sorted(job_ids) == sorted([job.job_id() for job in jobs]) 98 | 99 | 100 | @pytest.mark.parametrize( 101 | "creds,msg", 102 | [ 103 | ({}, "Credentials `token` not present in provider."), 104 | ({"token": None}, "Credentials `token` may not be None!"), 105 | ({"token": "something"}, "Credentials `url` not present in provider."), 106 | ( 107 | {"token": "something", "url": None}, 108 | "Credentials `url` may not be None!", 109 | ), 110 | ], 111 | ) 112 | def test_create_client_exceptions(mock_backend, creds, msg): 113 | """Test various exceptions that can be raised during client creation. 114 | 115 | Args: 116 | mock_backend (MockBackend): A fake/mock IonQBackend. 117 | creds (dict): A dictionary of bad credentials values. 118 | msg (str): An expected error for `creds`. 119 | """ 120 | fake_provider = mock.MagicMock() 121 | fake_provider.credentials = creds 122 | provider_patch = mock.patch.object(mock_backend, "_provider", fake_provider) 123 | with provider_patch, pytest.raises(exceptions.IonQCredentialsError) as exc_info: 124 | mock_backend.create_client() 125 | 126 | assert str(exc_info.value.message) == msg 127 | 128 | 129 | def test_run(mock_backend, requests_mock): 130 | """Test that the backend `run` submits a circuit and returns its job. 131 | 132 | Args: 133 | mock_backend (MockBackend): A fake/mock IonQBackend. 134 | requests_mock (:class:`request_mock.Mocker`): A requests mocker. 135 | """ 136 | path = mock_backend.client.make_path("jobs") 137 | dummy_response = conftest.dummy_job_response("fake_job") 138 | 139 | # Mock the call to submit: 140 | requests_mock.post(path, json=dummy_response, status_code=200) 141 | 142 | # Run a dummy circuit. 143 | qc = QuantumCircuit(1) 144 | qc.measure_all() 145 | job = mock_backend.run(qc) 146 | 147 | assert isinstance(job, ionq_job.IonQJob) 148 | assert job.job_id() == "fake_job" 149 | 150 | 151 | def test_run_single_element_list(mock_backend, requests_mock): 152 | """Test that the backend `run` submits a circuit in a single-element list. 153 | 154 | Args: 155 | mock_backend (MockBackend): A fake/mock IonQBackend. 156 | requests_mock (:class:`request_mock.Mocker`): A requests mocker. 157 | """ 158 | path = mock_backend.client.make_path("jobs") 159 | dummy_response = conftest.dummy_job_response("fake_job") 160 | 161 | # Mock the call to submit: 162 | requests_mock.post(path, json=dummy_response, status_code=200) 163 | 164 | # Run a dummy circuit. 165 | qc = QuantumCircuit(1) 166 | qc.measure_all() 167 | job = mock_backend.run([qc]) 168 | 169 | assert isinstance(job, ionq_job.IonQJob) 170 | assert job.job_id() == "fake_job" 171 | 172 | 173 | def test_run_extras(mock_backend, requests_mock): 174 | """Test that the backend `run` accepts an arbitrary parameter dictionary. 175 | 176 | Args: 177 | mock_backend (MockBackend): A fake/mock IonQBackend. 178 | requests_mock (:class:`request_mock.Mocker`): A requests mocker. 179 | """ 180 | path = mock_backend.client.make_path("jobs") 181 | dummy_response = conftest.dummy_job_response("fake_job") 182 | 183 | # Mock the call to submit: 184 | requests_mock.post(path, json=dummy_response, status_code=200) 185 | 186 | # Run a dummy circuit. 187 | qc = QuantumCircuit(1, metadata={"experiment": "abc123"}) 188 | qc.measure_all() 189 | job = mock_backend.run( 190 | qc, 191 | extra_query_params={ 192 | "error_mitigation": {"debias": True}, 193 | }, 194 | extra_metadata={ 195 | "iteration": "10", 196 | }, 197 | ) 198 | 199 | assert isinstance(job, ionq_job.IonQJob) 200 | assert job.job_id() == "fake_job" 201 | assert job.extra_query_params == { 202 | "error_mitigation": {"debias": True}, 203 | } 204 | assert job.extra_metadata == { 205 | "iteration": "10", 206 | } 207 | assert job.circuit.metadata == {"experiment": "abc123"} 208 | 209 | 210 | def test_warn_null_mappings(mock_backend, requests_mock): 211 | """Test that a circuit without measurements emits 212 | a warning. 213 | 214 | Args: 215 | mock_backend (MockBackend): A fake/mock IonQBackend. 216 | requests_mock (:class:`request_mock.Mocker`): A requests mocker. 217 | """ 218 | path = mock_backend.client.make_path("jobs") 219 | dummy_response = conftest.dummy_job_response("fake_job") 220 | 221 | # Mock the call to submit: 222 | requests_mock.post(path, json=dummy_response, status_code=200) 223 | 224 | # Create a circuit with no measurement gates 225 | qc = QuantumCircuit(1, 1) 226 | qc.h(0) 227 | 228 | with pytest.warns(UserWarning) as warninfo: 229 | mock_backend.run(qc) 230 | assert "Circuit is not measuring any qubits" in {str(w.message) for w in warninfo} 231 | 232 | 233 | def test_multiexp_job(mock_backend, requests_mock): 234 | """Test that the backend `run` handles more than one circuit. 235 | 236 | Args: 237 | mock_backend (MockBackend): A fake/mock IonQBackend. 238 | requests_mock (:class:`request_mock.Mocker`): A requests mocker. 239 | """ 240 | path = mock_backend.client.make_path("jobs") 241 | dummy_response = conftest.dummy_job_response("fake_job") 242 | 243 | # Mock the call to submit: 244 | requests_mock.post(path, json=dummy_response, status_code=200) 245 | 246 | # Run a dummy multi-experiment job. 247 | qc1 = QuantumCircuit(1, 1) 248 | qc1.h(0) 249 | qc1.measure(0, 0) 250 | qc2 = QuantumCircuit(1, 1) 251 | qc2.x(0) 252 | qc2.measure(0, 0) 253 | job = mock_backend.run([qc1, qc2]) 254 | 255 | # Verify json payload 256 | assert len(job.circuit) == 2 257 | assert len(requests_mock.request_history) == 1 258 | request = requests_mock.request_history[0] 259 | assert request.method == "POST" 260 | assert request.url == path 261 | request_json = request.json() 262 | assert "qiskit_header" in request_json["metadata"] 263 | # delete the qiskit_header field 264 | del request_json["metadata"]["qiskit_header"] 265 | assert request_json == { 266 | "target": "mock_backend", 267 | "shots": 1024, 268 | "name": f"{len(job.circuit)} circuits", 269 | "input": { 270 | "format": "ionq.circuit.v0", 271 | "gateset": "qis", 272 | "qubits": 1, 273 | "circuits": [ 274 | { 275 | "name": qc1.name, 276 | "circuit": [{"gate": "h", "targets": [0]}], 277 | "registers": {"meas_mapped": [0]}, 278 | }, 279 | { 280 | "name": qc2.name, 281 | "circuit": [{"gate": "x", "targets": [0]}], 282 | "registers": {"meas_mapped": [0]}, 283 | }, 284 | ], 285 | }, 286 | "metadata": { 287 | "shots": "1024", 288 | "sampler_seed": "None", 289 | }, 290 | } 291 | -------------------------------------------------------------------------------- /test/ionq_client/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | -------------------------------------------------------------------------------- /test/ionq_gates/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | -------------------------------------------------------------------------------- /test/ionq_gates/test_gates.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Tests for the IonQ's GPIGate, GPI2Gate, MSGate, ZZGate.""" 28 | # pylint: disable=redefined-outer-name 29 | 30 | import numpy as np 31 | 32 | import pytest 33 | 34 | from qiskit.circuit.library import XGate, YGate, RXGate, RYGate, HGate 35 | from qiskit_ionq import GPIGate, GPI2Gate, MSGate, ZZGate 36 | 37 | 38 | @pytest.mark.parametrize("gate,phase", [(XGate(), 0), (YGate(), 0.25)]) 39 | def test_gpi_equivalences(gate, phase): 40 | """Tests equivalence of the GPI gate at specific phases.""" 41 | gpi = GPIGate(phase) 42 | np.testing.assert_array_almost_equal(gate.to_matrix(), gpi.to_matrix()) 43 | 44 | 45 | @pytest.mark.parametrize( 46 | "gate,phase", [(RXGate(np.pi / 2), 1), (RYGate(np.pi / 2), 0.25)] 47 | ) 48 | def test_gpi2_equivalences(gate, phase): 49 | """Tests equivalence of the GPI2 gate at specific phases.""" 50 | gpi2 = GPI2Gate(phase) 51 | np.testing.assert_array_almost_equal(gate.to_matrix(), gpi2.to_matrix()) 52 | 53 | 54 | @pytest.mark.parametrize("gpi2_angle_1, gpi_angle, gpi2_angle_2", [(0, -0.125, 0.5)]) 55 | def test_hadamard_equivalence(gpi2_angle_1, gpi_angle, gpi2_angle_2): 56 | """Tests equivalence of the Hadamard gate with the GPI and GPI2 gates.""" 57 | gpi2_1 = GPI2Gate(gpi2_angle_1) 58 | gpi = GPIGate(gpi_angle) 59 | gpi2_2 = GPI2Gate(gpi2_angle_2) 60 | native_hadamard = np.dot(gpi2_2, np.dot(gpi, gpi2_1)) 61 | np.testing.assert_array_almost_equal(native_hadamard, HGate().to_matrix()) 62 | 63 | 64 | @pytest.mark.parametrize("phase", [0, 0.1, 0.4, np.pi / 2, np.pi, 2 * np.pi]) 65 | def test_gpi_inverse(phase): 66 | """Tests that the GPI gate is unitary.""" 67 | gate = GPIGate(phase) 68 | mat = np.array(gate) 69 | np.testing.assert_array_almost_equal(mat.dot(mat.conj().T), np.identity(2)) 70 | 71 | 72 | @pytest.mark.parametrize("phase", [0, 0.1, 0.4, np.pi / 2, np.pi, 2 * np.pi]) 73 | def test_gpi2_inverse(phase): 74 | """Tests that the GPI2 gate is unitary.""" 75 | gate = GPI2Gate(phase) 76 | 77 | mat = np.array(gate) 78 | np.testing.assert_array_almost_equal(mat.dot(mat.conj().T), np.identity(2)) 79 | 80 | 81 | @pytest.mark.parametrize( 82 | "params", 83 | [ 84 | (0, 1, 0.25), 85 | (0.1, 1, 0.25), 86 | (0.4, 1, 0.25), 87 | (np.pi / 2, 0, 0.25), 88 | (0, np.pi, 0.25), 89 | (0.1, 2 * np.pi, 0.25), 90 | ], 91 | ) 92 | def test_ms_inverse(params): 93 | """Tests that the MS gate is unitary.""" 94 | gate = MSGate(params[0], params[1], params[2]) 95 | 96 | mat = np.array(gate) 97 | np.testing.assert_array_almost_equal(mat.dot(mat.conj().T), np.identity(4)) 98 | 99 | 100 | @pytest.mark.parametrize( 101 | "angle", 102 | [0, 0.1, 0.4, np.pi / 2, np.pi, 2 * np.pi], 103 | ) 104 | def test_zz_inverse(angle): 105 | """Tests that the ZZ gate is unitary.""" 106 | gate = ZZGate(angle) 107 | 108 | mat = np.array(gate) 109 | np.testing.assert_array_almost_equal(mat.dot(mat.conj().T), np.identity(4)) 110 | -------------------------------------------------------------------------------- /test/ionq_job/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | -------------------------------------------------------------------------------- /test/ionq_optimizer_plugin/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | -------------------------------------------------------------------------------- /test/ionq_provider/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | -------------------------------------------------------------------------------- /test/ionq_provider/test_backend_service.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2019. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | """Test basic provider API methods.""" 27 | 28 | from qiskit_ionq import IonQProvider 29 | 30 | 31 | def test_provider_autocomplete(): 32 | """Verifies that provider.backends autocomplete works.""" 33 | pro = IonQProvider("123456") 34 | 35 | for backend in pro.backends(): 36 | assert hasattr(pro.backends, backend.name()) 37 | 38 | 39 | def test_provider_getbackend(): 40 | """Verifies that provider.get_backend works.""" 41 | pro = IonQProvider("123456") 42 | 43 | for backend in pro.backends(): 44 | qis = pro.get_backend(backend.name()) 45 | native = pro.get_backend(backend.name(), gateset="native") 46 | assert backend == qis 47 | assert backend != native 48 | 49 | 50 | def test_backend_eq(): 51 | """Verifies equality works for various backends""" 52 | pro = IonQProvider("123456") 53 | 54 | sub1 = pro.get_backend("ionq_qpu.sub-1") 55 | sub2 = pro.get_backend("ionq_qpu.sub-2") 56 | also_sub1 = pro.get_backend("ionq_qpu.sub-1") 57 | simulator = pro.get_backend("ionq_simulator") 58 | 59 | assert sub1 == also_sub1 60 | assert sub1 != sub2 61 | assert also_sub1 != sub2 62 | assert sub1 != simulator 63 | -------------------------------------------------------------------------------- /test/test_exceptions.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Test basic exceptions behavior""" 28 | 29 | import pickle 30 | from unittest import mock 31 | 32 | import pytest 33 | import requests 34 | 35 | from qiskit_ionq import exceptions 36 | from qiskit_ionq.exceptions import IonQRetriableError 37 | 38 | 39 | def test_base_str_and_repr(): 40 | """Test basic str and repr support.""" 41 | err = exceptions.IonQError() 42 | expected = "IonQError('')" 43 | assert str(err) == expected 44 | assert repr(err) == repr(expected) 45 | 46 | 47 | def test_gate_error_str_and_repr(): 48 | """Test that IonQAPIError has a str/repr that includes args.""" 49 | err = exceptions.IonQGateError("a gate", "b set") 50 | str_expected = ( 51 | "IonQGateError(\"gate 'a gate' is not supported on the 'b set' IonQ backends." 52 | " Please use the qiskit.transpile method, manually rewrite to remove the gate," 53 | ' or change the gateset selection as appropriate.")' 54 | ) 55 | repr_expected = "IonQGateError(gate_name='a gate', gateset='b set')" 56 | assert str(err) == str_expected 57 | assert repr(err) == repr_expected 58 | 59 | 60 | def test_api_error(): 61 | """Test that IonQAPIError has specific instance attributes.""" 62 | err = exceptions.IonQAPIError( 63 | message="an error", 64 | status_code=500, 65 | headers={"Content-Type": "text/html"}, 66 | body="Hello IonQ!", 67 | error_type="internal_error", 68 | ) 69 | assert err.message == "an error" 70 | assert err.status_code == 500 71 | assert err.headers == {"Content-Type": "text/html"} 72 | assert err.body == "Hello IonQ!" 73 | assert err.error_type == "internal_error" 74 | 75 | 76 | def test_api_error_str_and_repr(): 77 | """Test that IonQAPIError has a str/repr that includes args.""" 78 | err = exceptions.IonQAPIError( 79 | message="an error", 80 | status_code=500, 81 | headers={"Content-Type": "text/html"}, 82 | body="Hello IonQ!", 83 | error_type="internal_error", 84 | ) 85 | expected = ( 86 | "IonQAPIError(message='an error'," 87 | "status_code=500," 88 | "headers={'Content-Type': 'text/html'}," 89 | "body=Hello IonQ!," 90 | "error_type='internal_error')" 91 | ) 92 | assert str(err) == expected 93 | assert repr(err) == repr(expected) 94 | 95 | 96 | def test_api_error_from_response(): 97 | """Test that IonQAPIError can be made directly from a response JSON dict.""" 98 | # Create a response object 99 | response = requests.Response() 100 | response.status_code = 500 101 | response.headers = {"Content-Type": "text/html"} 102 | response._content = b"Hello IonQ!" 103 | # Mock the json method of the response object 104 | response.json = mock.MagicMock( 105 | return_value={ 106 | "error": { 107 | "message": "an error", 108 | "type": "internal_error", 109 | } 110 | } 111 | ) 112 | 113 | with pytest.raises(exceptions.IonQAPIError) as exc: 114 | raise exceptions.IonQAPIError.from_response(response) 115 | 116 | err = exc.value 117 | assert err.message == "an error" 118 | assert err.status_code == 500 119 | assert err.headers == {"Content-Type": "text/html"} 120 | assert err.body == "Hello IonQ!" 121 | assert err.error_type == "internal_error" 122 | 123 | 124 | def test_api_error_raise_for_status(): 125 | """Test that IonQAPIError can be made directly from a response JSON dict.""" 126 | # Create a request object 127 | request = requests.Request("POST", "https://api.ionq.co") 128 | prepared_request = request.prepare() 129 | # Create a response object 130 | response = requests.Response() 131 | response.status_code = 500 132 | response.headers = {"Content-Type": "text/html"} 133 | response._content = b"Hello IonQ!" 134 | # Set the request attribute of the response object 135 | response.request = prepared_request 136 | # Mock the json method of the response object 137 | response.json = mock.MagicMock( 138 | return_value={ 139 | "error": { 140 | "message": "an error", 141 | "type": "internal_error", 142 | } 143 | } 144 | ) 145 | 146 | with pytest.raises(exceptions.IonQRetriableError) as exc: 147 | exceptions.IonQAPIError.raise_for_status(response) 148 | 149 | err = exc.value._cause 150 | assert err.message == "an error" 151 | assert err.status_code == 500 152 | assert err.headers == {"Content-Type": "text/html"} 153 | assert err.body == "Hello IonQ!" 154 | assert err.error_type == "internal_error" 155 | 156 | 157 | def test_retriable_raise_for_status(): 158 | """Test that IonQAPIError can be made directly from a response JSON dict.""" 159 | # Create a request object 160 | request = requests.Request("POST", "https://api.ionq.co") 161 | prepared_request = request.prepare() 162 | # Create a response object 163 | response = requests.Response() 164 | response.status_code = 400 165 | response.headers = {"Content-Type": "text/html"} 166 | response._content = b"Hello IonQ!" 167 | # Set the request attribute of the response object 168 | response.request = prepared_request 169 | # Mock the json method of the response object 170 | response.json = mock.MagicMock( 171 | return_value={ 172 | "error": { 173 | "message": "an error", 174 | "type": "invalid_request", 175 | } 176 | } 177 | ) 178 | 179 | with pytest.raises(exceptions.IonQAPIError) as exc: 180 | exceptions.IonQAPIError.raise_for_status(response) 181 | 182 | err = exc.value 183 | assert err.message == "an error" 184 | assert err.status_code == 400 185 | assert err.headers == {"Content-Type": "text/html"} 186 | assert err.body == "Hello IonQ!" 187 | assert err.error_type == "invalid_request" 188 | assert err is not IonQRetriableError 189 | 190 | 191 | def test_error_format__code_message(): 192 | """Test the {"code": , "message": } error response format.""" 193 | # Create a response object 194 | response = requests.Response() 195 | response.status_code = 500 196 | response.headers = {"Content-Type": "text/html"} 197 | response._content = b"Hello IonQ!" 198 | # Mock the json method of the response object 199 | response.json = mock.MagicMock( 200 | return_value={ 201 | "code": 500, 202 | "message": "an error", 203 | } 204 | ) 205 | 206 | with pytest.raises(exceptions.IonQAPIError) as exc: 207 | raise exceptions.IonQAPIError.from_response(response) 208 | 209 | err = exc.value 210 | assert err.status_code == 500 211 | assert err.message == "an error" 212 | assert err.headers == {"Content-Type": "text/html"} 213 | assert err.body == "Hello IonQ!" 214 | assert err.error_type == "internal_error" 215 | 216 | 217 | def test_error_format_bad_request(): 218 | """Test the { "statusCode": , "error": , "message": } error response format.""" 219 | # Create a response object 220 | response = requests.Response() 221 | response.status_code = 500 222 | response.headers = {"Content-Type": "text/html"} 223 | response._content = b"Hello IonQ!" 224 | # Mock the json method of the response object 225 | response.json = mock.MagicMock( 226 | return_value={ 227 | "statusCode": 500, 228 | "error": "internal_error", 229 | "message": "an error", 230 | } 231 | ) 232 | 233 | with pytest.raises(exceptions.IonQAPIError) as exc: 234 | raise exceptions.IonQAPIError.from_response(response) 235 | 236 | err = exc.value 237 | assert err.status_code == 500 238 | assert err.message == "an error" 239 | assert err.headers == {"Content-Type": "text/html"} 240 | assert err.body == "Hello IonQ!" 241 | assert err.error_type == "internal_error" 242 | 243 | 244 | def test_error_format__nested_error(): 245 | """Test the { "error": { "type": , "message: } } error response format.""" 246 | # Create a response object 247 | response = requests.Response() 248 | response.status_code = 500 249 | response.headers = {"Content-Type": "text/html"} 250 | response._content = b"Hello IonQ!" 251 | # Mock the json method of the response object 252 | response.json = mock.MagicMock( 253 | return_value={ 254 | "error": { 255 | "message": "an error", 256 | "type": "internal_error", 257 | } 258 | } 259 | ) 260 | 261 | with pytest.raises(exceptions.IonQAPIError) as exc: 262 | raise exceptions.IonQAPIError.from_response(response) 263 | 264 | err = exc.value 265 | assert err.status_code == 500 266 | assert err.message == "an error" 267 | assert err.headers == {"Content-Type": "text/html"} 268 | assert err.body == "Hello IonQ!" 269 | assert err.error_type == "internal_error" 270 | 271 | 272 | def test_error_format__default(): 273 | """Test the when no known error format comes back.""" 274 | # Create a response object 275 | response = requests.Response() 276 | response.status_code = 500 277 | response.headers = {"Content-Type": "text/html"} 278 | response._content = b"Hello IonQ!" 279 | # Mock the json method of the response object 280 | response.json = mock.MagicMock(return_value={}) 281 | 282 | with pytest.raises(exceptions.IonQAPIError) as exc: 283 | raise exceptions.IonQAPIError.from_response(response) 284 | 285 | err = exc.value 286 | assert err.status_code == 500 287 | assert err.message == "No error details provided." 288 | assert err.headers == {"Content-Type": "text/html"} 289 | assert err.body == "Hello IonQ!" 290 | assert err.error_type == "internal_error" 291 | 292 | 293 | def test_serializing_error(): 294 | """Test that an error can be serialized and deserialized.""" 295 | 296 | err = exceptions.IonQAPIError( 297 | message="message", 298 | status_code=400, 299 | headers="headers", 300 | body="body", 301 | error_type="error_type", 302 | ) 303 | err2 = pickle.loads(pickle.dumps(err)) 304 | assert err.message == err2.message 305 | assert err.status_code == err2.status_code 306 | assert err.headers == err2.headers 307 | assert err.body == err2.body 308 | assert err.error_type == err2.error_type 309 | -------------------------------------------------------------------------------- /test/test_mock.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2020 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Test basic request_mock behavior.""" 28 | 29 | import unittest 30 | 31 | import pytest 32 | import requests 33 | from requests_mock import adapter as rm_adapter 34 | 35 | 36 | def test_global_mock(): 37 | """test the global requests mock""" 38 | response = requests.get("https://www.google.com", timeout=30) 39 | assert response.text == "UNHANDLED REQUEST. PLEASE MOCK WITH requests_mock." 40 | 41 | 42 | def test_fixture_mock(requests_mock): 43 | """Test a function-scoped mock overrides the global. 44 | 45 | Args: 46 | requests_mock (:class:`requests_mock.Mocker`): A requests mocker. 47 | """ 48 | requests_mock.get("https://www.google.com", text="function mock") 49 | response = requests.get("https://www.google.com", timeout=30) 50 | assert response.text == "function mock" 51 | 52 | 53 | class TestUnittestCompatibility(unittest.TestCase): 54 | """An example of how to use `requests_mock` with a class. 55 | 56 | Attributes: 57 | requests_mock (:class:`requests_mock.Mocker`): A requests mocker. 58 | """ 59 | 60 | requests_mock = None 61 | 62 | @pytest.fixture(autouse=True) 63 | def init_requests_mock(self, requests_mock): 64 | """Initialize a :class:`requests_mock.Mocker` for this class. 65 | 66 | .. NOTE:: 67 | Because this is cached on a class, it means all requests made 68 | from any methods on this class will use the same registered mocks. 69 | 70 | Args: 71 | requests_mock (:class:`requests_mock.Mocker`): 72 | A requests_mock fixture. 73 | """ 74 | self.requests_mock = requests_mock 75 | 76 | # Register any URIs you want to mock for the entire class here: 77 | self.requests_mock.register_uri( 78 | rm_adapter.ANY, 79 | rm_adapter.ANY, 80 | response_list=[ 81 | { 82 | "status_code": 599, 83 | "text": "class fixture mock", 84 | } 85 | ], 86 | ) 87 | 88 | def test_class_mock(self): 89 | """ 90 | Test the class-scoped requests mock, which is setup 91 | in :meth:`init_requests_mock` 92 | """ 93 | response = requests.get("https://www.google.com", timeout=30) 94 | self.assertEqual(response.text, "class fixture mock") 95 | 96 | def test_method_mock(self): 97 | """Test a method-scoped mock overrides the global.""" 98 | self.requests_mock.get( 99 | "https://www.google.com", text="instance method fixture mock" 100 | ) 101 | response = requests.get("https://www.google.com", timeout=30) 102 | self.assertEqual(response.text, "instance method fixture mock") 103 | 104 | def test_another_method_mock(self): 105 | """Test a second method-scoped mock overrides the global.""" 106 | self.requests_mock.get( 107 | "https://www.google.com", 108 | text="another instance method fixture mock", 109 | ) 110 | response = requests.get("https://www.google.com", timeout=30) 111 | self.assertEqual(response.text, "another instance method fixture mock") 112 | -------------------------------------------------------------------------------- /test/transpile_ionq_gates/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2024 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | -------------------------------------------------------------------------------- /test/transpile_ionq_gates/test_transpile_ionq_gates.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2017, 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # Copyright 2024 IonQ, Inc. (www.ionq.com) 14 | # 15 | # Licensed under the Apache License, Version 2.0 (the "License"); 16 | # you may not use this file except in compliance with the License. 17 | # You may obtain a copy of the License at 18 | # 19 | # http://www.apache.org/licenses/LICENSE-2.0 20 | # 21 | # Unless required by applicable law or agreed to in writing, software 22 | # distributed under the License is distributed on an "AS IS" BASIS, 23 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | # See the License for the specific language governing permissions and 25 | # limitations under the License. 26 | 27 | """Test transpilation to native gatesets.""" 28 | 29 | import collections.abc 30 | import numpy as np 31 | import pytest 32 | 33 | from qiskit import ( 34 | QuantumCircuit, 35 | QuantumRegister, 36 | transpile, 37 | ) 38 | from qiskit.quantum_info import Statevector 39 | from qiskit.circuit.library import ( 40 | HGate, 41 | IGate, 42 | PhaseGate, 43 | RGate, 44 | RXGate, 45 | RYGate, 46 | RZGate, 47 | SGate, 48 | SdgGate, 49 | SXGate, 50 | SXdgGate, 51 | TGate, 52 | TdgGate, 53 | UGate, 54 | U1Gate, 55 | U2Gate, 56 | U3Gate, 57 | XGate, 58 | YGate, 59 | ZGate, 60 | CHGate, 61 | CPhaseGate, 62 | CRXGate, 63 | RXXGate, 64 | CRYGate, 65 | RYYGate, 66 | CRZGate, 67 | RZZGate, 68 | RZXGate, 69 | XXMinusYYGate, 70 | XXPlusYYGate, 71 | ECRGate, 72 | CSGate, 73 | CSdgGate, 74 | SwapGate, 75 | iSwapGate, 76 | DCXGate, 77 | CUGate, 78 | CU1Gate, 79 | CU3Gate, 80 | CXGate, 81 | CYGate, 82 | CZGate, 83 | ) 84 | from qiskit_ionq import ionq_provider 85 | 86 | # Mapping from gate names to gate classes 87 | gate_map = { 88 | # single-qubit gates 89 | "HGate": HGate, 90 | "IGate": IGate, 91 | "PhaseGate": PhaseGate, 92 | "RGate": RGate, 93 | "RXGate": RXGate, 94 | "RYGate": RYGate, 95 | "RZGate": RZGate, 96 | "SGate": SGate, 97 | "SdgGate": SdgGate, 98 | "SXGate": SXGate, 99 | "SXdgGate": SXdgGate, 100 | "TGate": TGate, 101 | "TdgGate": TdgGate, 102 | "UGate": UGate, 103 | "U1Gate": U1Gate, 104 | "U2Gate": U2Gate, 105 | "U3Gate": U3Gate, 106 | "XGate": XGate, 107 | "YGate": YGate, 108 | "ZGate": ZGate, 109 | # multi-qubit gates 110 | "CHGate": CHGate, 111 | "CPhaseGate": CPhaseGate, 112 | "CRXGate": CRXGate, 113 | "RXXGate": RXXGate, 114 | "CRYGate": CRYGate, 115 | "RYYGate": RYYGate, 116 | "CRZGate": CRZGate, 117 | "RZZGate": RZZGate, 118 | "RZXGate": RZXGate, 119 | "XXMinusYYGate": XXMinusYYGate, 120 | "XXPlusYYGate": XXPlusYYGate, 121 | "ECRGate": ECRGate, 122 | "CSGate": CSGate, 123 | "CSdgGate": CSdgGate, 124 | "SwapGate": SwapGate, 125 | "iSwapGate": iSwapGate, 126 | "DCXGate": DCXGate, 127 | "CUGate": CUGate, 128 | "CU1Gate": CU1Gate, 129 | "CU3Gate": CU3Gate, 130 | "CXGate": CXGate, 131 | "CYGate": CYGate, 132 | "CZGate": CZGate, 133 | } 134 | 135 | 136 | def append_gate(circuit, gate_name, param, qubits): 137 | """Append a gate to a circuit.""" 138 | gate_class = gate_map[gate_name] 139 | if param is not None: 140 | if isinstance(param, collections.abc.Sequence): 141 | circuit.append(gate_class(*param), qubits) 142 | else: 143 | circuit.append(gate_class(param), qubits) 144 | else: 145 | circuit.append(gate_class(), qubits) 146 | 147 | 148 | @pytest.mark.parametrize( 149 | "ideal_results, gates", 150 | [ 151 | # single-qubit gates 152 | ([0.5, 0.5], [("HGate", None)]), 153 | ([1, 0], [("IGate", None)]), 154 | ([1, 0], [("PhaseGate", 0.25)]), 155 | ([0.984, 0.016], [("RGate", [0.25, 0.5])]), 156 | ([0.984, 0.016], [("RXGate", 0.25)]), 157 | ([0.984, 0.016], [("RYGate", 0.25)]), 158 | ([1, 0], [("RZGate", 0.25)]), 159 | ([1, 0], [("SGate", None)]), 160 | ([1, 0], [("SdgGate", None)]), 161 | ([0.5, 0.5], [("SXGate", None)]), 162 | ([0.5, 0.5], [("SXdgGate", None)]), 163 | ([1, 0], [("TGate", None)]), 164 | ([1, 0], [("TdgGate", None)]), 165 | ([0.984, 0.016], [("UGate", [0.25, 0.5, 0.75])]), 166 | ([1, 0], [("U1Gate", 0.25)]), 167 | ([0.5, 0.5], [("U2Gate", [0.25, 0.5])]), 168 | ([0.984, 0.016], [("U3Gate", [0.25, 0.5, 0.75])]), 169 | ([0, 1], [("XGate", None)]), 170 | ([0, 1], [("YGate", None)]), 171 | ([1, 0], [("ZGate", None)]), 172 | # sequence of single-qubit gates 173 | ( 174 | [0.966, 0.034], 175 | [ 176 | ("HGate", None), 177 | ("IGate", None), 178 | ("PhaseGate", 0.25), 179 | ("RGate", [0.25, 0.5]), 180 | ("RXGate", 0.25), 181 | ("RYGate", 0.25), 182 | ("RZGate", 0.25), 183 | ("SGate", None), 184 | ("SdgGate", None), 185 | ("SXGate", None), 186 | ("SXdgGate", None), 187 | ("TGate", None), 188 | ("TdgGate", None), 189 | ("UGate", [0.25, 0.5, 0.75]), 190 | ("U1Gate", 0.25), 191 | ("U2Gate", [0.25, 0.5]), 192 | ("U3Gate", [0.25, 0.5, 0.75]), 193 | ("XGate", None), 194 | ("YGate", None), 195 | ("ZGate", None), 196 | ], 197 | ), 198 | ], 199 | ids=lambda val: f"{val}", 200 | ) 201 | def test_single_qubit_transpilation(ideal_results, gates): 202 | """Test transpiling single-qubit circuits to native gates.""" 203 | # create a quantum circuit 204 | qr = QuantumRegister(1) 205 | circuit = QuantumCircuit(qr) 206 | for gate_name, param in gates: 207 | append_gate(circuit, gate_name, param, [0]) 208 | 209 | # transpile circuit to native gates 210 | provider = ionq_provider.IonQProvider() 211 | backend = provider.get_backend("ionq_simulator", gateset="native") 212 | transpiled_circuit = transpile(circuit, backend) 213 | 214 | # simulate the circuit 215 | statevector = Statevector(transpiled_circuit) 216 | probabilities = np.abs(statevector) ** 2 217 | np.testing.assert_allclose( 218 | probabilities, 219 | ideal_results, 220 | atol=1e-3, 221 | err_msg=( 222 | f"Ideal: {np.round(ideal_results, 3)},\n" 223 | f"Actual: {np.round(probabilities, 3)},\n" 224 | f"Circuit: {circuit}" 225 | ), 226 | ) 227 | 228 | 229 | @pytest.mark.parametrize( 230 | "ideal_results, gates", 231 | [ 232 | # two-qubit gates 233 | ( 234 | [0.984, 0.008, 0, 0.008], 235 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("CHGate", None, [0, 1])], 236 | ), 237 | ( 238 | [0.984, 0.016, 0, 0], 239 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("CPhaseGate", 0.25, [0, 1])], 240 | ), 241 | ( 242 | [0.984, 0.016, 0, 0], 243 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("CRXGate", 0.25, [0, 1])], 244 | ), 245 | ( 246 | [0.969, 0.015, 0, 0.015], 247 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("RXXGate", 0.25, [0, 1])], 248 | ), 249 | ( 250 | [0.984, 0.016, 0, 0], 251 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("CRYGate", 0.25, [0, 1])], 252 | ), 253 | ( 254 | [0.969, 0.015, 0, 0.015], 255 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("RYYGate", 0.25, [0, 1])], 256 | ), 257 | ( 258 | [0.984, 0.016, 0, 0], 259 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("CRZGate", 0.25, [0, 1])], 260 | ), 261 | ( 262 | [0.984, 0.016, 0, 0], 263 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("RZZGate", 0.25, [0, 1])], 264 | ), 265 | ( 266 | [0.969, 0.015, 0.015, 0], 267 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("RZXGate", 0.25, [0, 1])], 268 | ), 269 | ( 270 | [0.969, 0.016, 0, 0.015], 271 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("XXMinusYYGate", 0.25, [0, 1])], 272 | ), 273 | ( 274 | [0.984, 0.016, 0, 0], 275 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("XXPlusYYGate", 0.25, [0, 1])], 276 | ), 277 | ( 278 | [0.008, 0.492, 0.008, 0.492], 279 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("ECRGate", None, [0, 1])], 280 | ), 281 | ( 282 | [0.984, 0.016, 0, 0], 283 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("CSGate", None, [0, 1])], 284 | ), 285 | ( 286 | [0.984, 0.016, 0, 0], 287 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("CSdgGate", None, [0, 1])], 288 | ), 289 | ( 290 | [0.984, 0, 0.016, 0], 291 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("SwapGate", None, [0, 1])], 292 | ), 293 | ( 294 | [0.984, 0, 0.016, 0], 295 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("iSwapGate", None, [0, 1])], 296 | ), 297 | ( 298 | [0.984, 0, 0.016, 0], 299 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("DCXGate", None, [0, 1])], 300 | ), 301 | ( 302 | [0.984, 0.016, 0, 0], 303 | [ 304 | ("U3Gate", [0.25, 0.5, 0.75], [0]), 305 | ("CUGate", [0.25, 0.5, 0.75, 1], [0, 1]), 306 | ], 307 | ), 308 | ( 309 | [0.984, 0.016, 0, 0], 310 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("CU1Gate", 0.25, [0, 1])], 311 | ), 312 | ( 313 | [0.984, 0.016, 0, 0], 314 | [ 315 | ("U3Gate", [0.25, 0.5, 0.75], [0]), 316 | ("CU3Gate", [0.25, 0.5, 0.75], [0, 1]), 317 | ], 318 | ), 319 | ( 320 | [0.984, 0, 0, 0.016], 321 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("CXGate", None, [0, 1])], 322 | ), 323 | ( 324 | [0.984, 0, 0, 0.016], 325 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("CYGate", None, [0, 1])], 326 | ), 327 | ( 328 | [0.984, 0.016, 0, 0], 329 | [("U3Gate", [0.25, 0.5, 0.75], [0]), ("CZGate", None, [0, 1])], 330 | ), 331 | # sequence of two-qubit gates 332 | ( 333 | [0.012, 0.619, 0.350, 0.019], 334 | [ 335 | ("U3Gate", [0.25, 0.5, 0.75], [0]), 336 | ("CHGate", None, [0, 1]), 337 | ("CPhaseGate", 0.25, [0, 1]), 338 | ("CRXGate", 0.25, [0, 1]), 339 | ("RXXGate", 0.25, [0, 1]), 340 | ("CRYGate", 0.25, [0, 1]), 341 | ("RYYGate", 0.25, [0, 1]), 342 | ("CRZGate", 0.25, [0, 1]), 343 | ("RZZGate", 0.25, [0, 1]), 344 | ("RZXGate", 0.25, [0, 1]), 345 | ("XXMinusYYGate", 0.25, [0, 1]), 346 | ("XXPlusYYGate", 0.25, [0, 1]), 347 | ("ECRGate", None, [0, 1]), 348 | ("CSGate", None, [0, 1]), 349 | ("CSdgGate", None, [0, 1]), 350 | ("SwapGate", None, [0, 1]), 351 | ("iSwapGate", None, [0, 1]), 352 | ("DCXGate", None, [0, 1]), 353 | ("CUGate", [0.25, 0.5, 0.75, 1], [0, 1]), 354 | ("CU1Gate", 0.25, [0, 1]), 355 | ("CU3Gate", [0.25, 0.5, 0.75], [0, 1]), 356 | ("CXGate", None, [0, 1]), 357 | ("CYGate", None, [0, 1]), 358 | ("CZGate", None, [0, 1]), 359 | ], 360 | ), 361 | ], 362 | ids=lambda val: f"{val}", 363 | ) 364 | def test_multi_qubit_transpilation(ideal_results, gates): 365 | """Test transpiling multi-qubit circuits to native gates.""" 366 | # create a quantum circuit 367 | qr = QuantumRegister(2) 368 | circuit = QuantumCircuit(qr) 369 | for gate_name, param, qubits in gates: 370 | append_gate(circuit, gate_name, param, qubits) 371 | 372 | # transpile circuit to native gates 373 | provider = ionq_provider.IonQProvider() 374 | backend = provider.get_backend("ionq_simulator", gateset="native") 375 | # Using optmization level 0 below is important here because ElidePermutations transpiler pass 376 | # in Qiskit will remove swap gates and instead premute qubits if optimization level is 2 or 3. 377 | # In the future this feature could be extended to optmization level 1 as well. 378 | transpiled_circuit = transpile(circuit, backend, optimization_level=0) 379 | 380 | # simulate the circuit 381 | statevector = Statevector(transpiled_circuit) 382 | probabilities = np.abs(statevector) ** 2 383 | np.testing.assert_allclose( 384 | probabilities, 385 | ideal_results, 386 | atol=1e-3, 387 | err_msg=( 388 | f"Ideal: {np.round(ideal_results, 3)},\n" 389 | f"Actual: {np.round(probabilities, 3)},\n" 390 | f"Circuit: {circuit}" 391 | ), 392 | ) 393 | -------------------------------------------------------------------------------- /tools/verify_headers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # This code is part of Qiskit. 3 | # 4 | # (C) Copyright IBM 2020 5 | # 6 | # This code is licensed under the Apache License, Version 2.0. You may 7 | # obtain a copy of this license in the LICENSE.txt file in the root directory 8 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 9 | # 10 | # Any modifications or derivative works of this code must retain this 11 | # copyright notice, and modified files need to carry a notice indicating 12 | # that they have been altered from the originals. 13 | 14 | import argparse 15 | import multiprocessing 16 | import os 17 | import sys 18 | import re 19 | 20 | # regex for character encoding from PEP 263 21 | pep263 = re.compile(r"^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)") 22 | 23 | 24 | def discover_files(code_paths): 25 | out_paths = [] 26 | for path in code_paths: 27 | if os.path.isfile(path): 28 | out_paths.append(path) 29 | else: 30 | for directory in os.walk(path): 31 | dir_path = directory[0] 32 | for subfile in directory[2]: 33 | if subfile.endswith(".py") or subfile.endswith(".pyx"): 34 | out_paths.append(os.path.join(dir_path, subfile)) 35 | return out_paths 36 | 37 | 38 | def validate_header(file_path): 39 | header = """# This code is part of Qiskit. 40 | # 41 | """ 42 | apache_text = """# 43 | # This code is licensed under the Apache License, Version 2.0. You may 44 | # obtain a copy of this license in the LICENSE.txt file in the root directory 45 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 46 | # 47 | # Any modifications or derivative works of this code must retain this 48 | # copyright notice, and modified files need to carry a notice indicating 49 | # that they have been altered from the originals. 50 | """ 51 | count = 0 52 | with open(file_path, encoding="utf8") as fd: 53 | lines = fd.readlines() 54 | start = 0 55 | for index, line in enumerate(lines): 56 | count += 1 57 | if count > 5: 58 | return file_path, False, "Header not found in first 5 lines" 59 | if count <= 2 and pep263.match(line): 60 | return ( 61 | file_path, 62 | False, 63 | "Unnecessary encoding specification (PEP 263, 3120)", 64 | ) 65 | if line == "# This code is part of Qiskit.\n": 66 | start = index 67 | break 68 | if "".join(lines[start : start + 2]) != header: 69 | return ( 70 | file_path, 71 | False, 72 | "Header up to copyright line does not match: %s" % header, 73 | ) 74 | if not lines[start + 2].startswith("# (C) Copyright IBM 20"): 75 | return (file_path, False, "Header copyright line not found") 76 | if "".join(lines[start + 3 : start + 11]) != apache_text: 77 | return ( 78 | file_path, 79 | False, 80 | "Header apache text string doesn't match:\n %s" % apache_text, 81 | ) 82 | return (file_path, True, None) 83 | 84 | 85 | def main(): 86 | default_path = os.path.join( 87 | os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "qiskit" 88 | ) 89 | parser = argparse.ArgumentParser(description="Check file headers.") 90 | parser.add_argument( 91 | "paths", 92 | type=str, 93 | nargs="*", 94 | default=[default_path], 95 | help="Paths to scan by default uses ../qiskit from the" " script", 96 | ) 97 | args = parser.parse_args() 98 | files = discover_files(args.paths) 99 | pool = multiprocessing.Pool() 100 | res = pool.map(validate_header, files) 101 | failed_files = [x for x in res if x[1] is False] 102 | if len(failed_files) > 0: 103 | for failed_file in failed_files: 104 | sys.stderr.write("%s failed header check because:\n" % failed_file[0]) 105 | sys.stderr.write("%s\n\n" % failed_file[2]) 106 | sys.exit(1) 107 | sys.exit(0) 108 | 109 | 110 | if __name__ == "__main__": 111 | main() 112 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py39, py310, py311, py312, py313, lint, docs 3 | 4 | [gh-actions] 5 | python = 6 | 3.9: py39, mypy 7 | 3.10: py310, mypy 8 | 3.11: py311, mypy 9 | 3.12: py312, mypy 10 | 3.13: py313, mypy 11 | 12 | [testenv] 13 | deps = 14 | .[test] 15 | -r requirements.txt 16 | -r requirements-test.txt 17 | usedevelop = true 18 | setenv = 19 | VIRTUAL_ENV={envdir} 20 | LANGUAGE=en_US 21 | LC_ALL=en_US.utf-8 22 | commands = pytest 23 | 24 | [testenv:lint] 25 | deps = 26 | pylint 27 | -r requirements.txt 28 | -r requirements-test.txt 29 | commands = 30 | pylint -rn --rcfile={toxinidir}/.pylintrc qiskit_ionq test 31 | python tools/verify_headers.py qiskit_ionq test 32 | 33 | [testenv:docs] 34 | allowlist_externals = make 35 | envdir = .tox/docs 36 | deps = 37 | -r requirements-docs.txt 38 | commands = make html 39 | --------------------------------------------------------------------------------