├── .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 |
4 |
5 | [](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 |
--------------------------------------------------------------------------------