├── .ado └── release.yml ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE.md └── workflows │ └── tests.yml ├── .gitignore ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── NOTICE.txt ├── README.md ├── SECURITY.md ├── docs ├── Makefile ├── conf.py ├── contributing.rst ├── history.rst ├── index.rst ├── installation.rst ├── make.bat ├── readme.rst └── usage.rst ├── pyproject.toml ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── src └── qiskit_qir │ ├── __init__.py │ ├── capability.py │ ├── elements.py │ ├── translate.py │ └── visitor.py ├── tests ├── conftest.py ├── resources │ ├── test_single_clbit_variations_falsy.ll │ ├── test_single_clbit_variations_truthy.ll │ ├── test_single_register_index_variations_falsy.ll │ ├── test_single_register_index_variations_truthy.ll │ ├── test_single_register_variations_falsy.ll │ ├── test_single_register_variations_truthy.ll │ ├── test_two_bit_register_variations_falsy.ll │ ├── test_two_bit_register_variations_three.ll │ ├── test_two_bit_register_variations_truthy.ll │ └── test_two_bit_register_variations_two.ll ├── test_batching.py ├── test_c_if.py ├── test_capabilities.py ├── test_circuits │ ├── __init__.py │ ├── basic_circuits.py │ ├── basic_gates.py │ ├── control_flow_circuits.py │ └── random.py ├── test_output.py ├── test_qiskit_qir.py └── test_utils.py └── tox.ini /.ado/release.yml: -------------------------------------------------------------------------------- 1 | name: qiskit-qir-publish-$(BuildId) 2 | 3 | # This pipeline is used to build and publish the PyQIR package 4 | # It uses a Microsoft ADO template for additional security checks. 5 | 6 | # Run on merges to main to ensure that the latest code 7 | # is always able to be published. 8 | trigger: 9 | branches: 10 | include: 11 | - main 12 | 13 | # Run the pipeline every day at 7:00 AM UTC to ensure 14 | # codeql and other governance checks are up-to-date. 15 | schedules: 16 | - cron: "0 7 * * *" 17 | displayName: 'Build for Component Governance' 18 | branches: 19 | include: 20 | - main 21 | always: true 22 | 23 | resources: 24 | repositories: 25 | - repository: 1ESPipelineTemplates 26 | type: git 27 | name: 1ESPipelineTemplates/1ESPipelineTemplates 28 | ref: refs/tags/release 29 | 30 | parameters: 31 | - name: hosts 32 | type: object 33 | default: 34 | - name: linux_x86_64 35 | poolName: 'Azure-Pipelines-DevTools-EO' 36 | imageName: 'ubuntu-latest' 37 | os: linux 38 | - name: python 39 | type: object 40 | default: 41 | - name: Python38 42 | version_minor: 8 43 | - name: Python39 44 | version_minor: 9 45 | - name: Python310 46 | version_minor: 10 47 | - name: Python311 48 | version_minor: 11 49 | 50 | extends: 51 | template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates 52 | parameters: 53 | sdl: 54 | sourceAnalysisPool: 55 | name: 'Azure-Pipelines-DevTools-EO' 56 | image: windows-2022 57 | os: windows 58 | stages: 59 | - stage: build 60 | displayName: Build 61 | jobs: 62 | - job: "Build_Python_Package" 63 | pool: 64 | name: 'Azure-Pipelines-DevTools-EO' 65 | image: 'ubuntu-latest' 66 | os: linux 67 | timeoutInMinutes: 90 68 | templateContext: 69 | outputs: 70 | - output: pipelineArtifact 71 | displayName: 'Upload Python Package Artifact' 72 | targetPath: $(System.DefaultWorkingDirectory)/dist 73 | artifactName: dist 74 | condition: succeeded() 75 | steps: 76 | - task: UsePythonVersion@0 77 | inputs: 78 | versionSpec: '3.11' 79 | displayName: 'Use Python 3.11' 80 | 81 | - script: | 82 | python -m pip install --upgrade pip 83 | python -m pip install -r requirements_dev.txt 84 | displayName: 'Install dependencies' 85 | 86 | - script: | 87 | make dist 88 | displayName: 'Build package' 89 | 90 | - stage: test 91 | displayName: test 92 | dependsOn: build 93 | condition: succeeded() 94 | jobs: 95 | - ${{ each host in parameters.hosts }}: 96 | - ${{ each python in parameters.python }}: 97 | - job: Tests_${{ python.name }}_${{ host.name }}_job 98 | pool: 99 | name: ${{ host.poolName }} 100 | image: ${{ host.imageName }} 101 | os: ${{ host.os }} 102 | templateContext: 103 | inputs: # All input build artifacts must be declared here 104 | - input: pipelineArtifact 105 | artifactName: dist 106 | targetPath: $(System.DefaultWorkingDirectory)/dist 107 | steps: 108 | - task: UsePythonVersion@0 109 | inputs: 110 | versionSpec: '3.${{ python.version_minor }}' 111 | displayName: 'Use Python 3.${{ python.version_minor }}' 112 | - script: | 113 | python -m pip install --upgrade pip 114 | python -m pip install -r requirements_dev.txt 115 | displayName: 'Install dependencies' 116 | - script: | 117 | python -m pip install dist/*.whl 118 | displayName: 'Install package' 119 | - script: | 120 | pytest 121 | displayName: 'Test package' 122 | 123 | - stage: approval 124 | displayName: Approval 125 | dependsOn: test 126 | condition: and(succeeded(), eq(variables['Build.Reason'], 'Manual')) 127 | jobs: 128 | - job: "Approval" 129 | pool: server 130 | timeoutInMinutes: 1440 # job times out in 1 day 131 | steps: 132 | - task: ManualValidation@0 133 | timeoutInMinutes: 1440 # task times out in 1 day 134 | inputs: 135 | notifyUsers: '' 136 | instructions: 'Please verify artifacts and approve the release' 137 | onTimeout: 'reject' 138 | 139 | - stage: release 140 | displayName: Release 141 | dependsOn: approval 142 | condition: and(succeeded(), eq(variables['Build.Reason'], 'Manual')) 143 | jobs: 144 | # We will get a warning about extra files in the sbom validation saying it failed. 145 | # This is expected as we have the wheels being downloaded to the same directory. 146 | # So each successive wheel will have the previous wheel in the directory and each 147 | # will be flagged as an extra file. See: 148 | # http://aka.ms/drop-validation-failure-additional-files 149 | - job: "Publish_Python_Packages" 150 | pool: 151 | name: 'Azure-Pipelines-DevTools-EO' 152 | image: 'ubuntu-latest' 153 | os: linux 154 | templateContext: 155 | type: releaseJob 156 | isProduction: true 157 | inputs: # All input build artifacts must be declared here 158 | - input: pipelineArtifact 159 | artifactName: dist 160 | targetPath: $(System.DefaultWorkingDirectory)/dist 161 | steps: 162 | - script: | 163 | ls $(System.DefaultWorkingDirectory)/dist 164 | displayName: Display Py Artifacts in Publishing Dir 165 | 166 | - task: EsrpRelease@4 167 | condition: succeeded() 168 | displayName: Publish Py Packages 169 | inputs: 170 | ConnectedServiceName: 'ESRP_Release' 171 | Intent: 'PackageDistribution' 172 | ContentType: 'PyPi' 173 | FolderLocation: '$(System.DefaultWorkingDirectory)/dist' 174 | Owners: '$(OwnerPersonalAlias)@microsoft.com' # NB: Group email here fails the task with non-actionable output. 175 | Approvers: 'billti@microsoft.com' 176 | # Auto-inserted Debugging defaults: 177 | ServiceEndpointUrl: 'https://api.esrp.microsoft.com' 178 | MainPublisher: 'QuantumDevelpmentKit' # ESRP Team's Correction (including the critical typo "Develpm"). 179 | DomainTenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47' 180 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * qiskit-qir version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: 4 | # Triggers the workflow on all PRs and push events to the main branch 5 | pull_request: 6 | push: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | python-version: ['3.8', '3.9', '3.10', '3.11'] 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Build package 23 | run: | 24 | python -m pip install --upgrade pip 25 | python -m pip install -r requirements_dev.txt 26 | make dist 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install dist/*.whl 30 | - name: Run tests 31 | run: pytest 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | docs/modules.rst 16 | docs/qiskit_qir.rst 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | test_output.*/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # dotenv 87 | .env 88 | 89 | # virtualenv 90 | .venv 91 | venv/ 92 | ENV/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | 107 | # IDE settings 108 | .vscode/ 109 | .idea/ 110 | 111 | .DS_Store 112 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/microsoft/qiskit-qir/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | qiskit-qir could always use more documentation, whether as part of the 42 | official qiskit-qir docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/microsoft/qiskit-qir/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `qiskit-qir` for local development. 61 | 62 | 1. Fork the `qiskit-qir` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/qiskit-qir.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv qiskit-qir 70 | $ cd qiskit-qir/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the 80 | tests, including testing other Python versions with tox:: 81 | 82 | $ flake8 src tests 83 | $ python setup.py test or pytest 84 | $ tox 85 | 86 | To get flake8 and tox, just pip install them into your virtualenv. 87 | 88 | 6. Commit your changes and push your branch to GitHub:: 89 | 90 | $ git add . 91 | $ git commit -m "Your detailed description of your changes." 92 | $ git push origin name-of-your-bugfix-or-feature 93 | 94 | 7. Submit a pull request through the GitHub website. 95 | 96 | Pull Request Guidelines 97 | ----------------------- 98 | 99 | Before you submit a pull request, check that it meets these guidelines: 100 | 101 | 1. The pull request should include tests. 102 | 2. If the pull request adds functionality, the docs should be updated. Put 103 | your new functionality into a function with a docstring, and add the 104 | feature to the list in README.rst. 105 | 3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8, and for PyPy. Check 106 | https://travis-ci.com/microsoft/qiskit-qir/pull_requests 107 | and make sure that the tests pass for all supported Python versions. 108 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.1.0 (2022-03-10) 6 | ------------------ 7 | 8 | * First release on PyPI. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CONTRIBUTING.rst 2 | include HISTORY.rst 3 | include LICENSE 4 | include README.rst 5 | 6 | recursive-include tests * 7 | recursive-exclude * __pycache__ 8 | recursive-exclude * *.py[co] 9 | 10 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | 6 | .PHONY: clean clean-build clean-pyc clean-test coverage deps dist docs help install lint lint/flake8 lint/black release test test-all test-release venv 7 | .DEFAULT_GOAL := help 8 | 9 | define BROWSER_PYSCRIPT 10 | import os, webbrowser, sys 11 | 12 | from urllib.request import pathname2url 13 | 14 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 15 | endef 16 | export BROWSER_PYSCRIPT 17 | 18 | define PRINT_HELP_PYSCRIPT 19 | import re, sys 20 | 21 | for line in sys.stdin: 22 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 23 | if match: 24 | target, help = match.groups() 25 | print("%-20s %s" % (target, help)) 26 | endef 27 | export PRINT_HELP_PYSCRIPT 28 | 29 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 30 | 31 | VENV = .venv 32 | VENV_PYTHON = $(VENV)/bin/python 33 | SYSTEM_PYTHON = $(or $(shell which python3), $(shell which python)) 34 | PYTHON = $(or $(wildcard $(VENV_PYTHON)), $(SYSTEM_PYTHON)) 35 | 36 | $(VENV_PYTHON): 37 | rm -rf $(VENV) 38 | $(SYSTEM_PYTHON) -m venv --upgrade-deps $(VENV) 39 | 40 | help: 41 | $(PYTHON) -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 42 | 43 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 44 | 45 | clean-build: ## remove build artifacts 46 | rm -fr build/ 47 | rm -fr dist/ 48 | rm -fr .eggs/ 49 | find . -name '*.egg-info' -exec rm -fr {} + 50 | find . -name '*.egg' -exec rm -f {} + 51 | 52 | clean-pyc: ## remove Python file artifacts 53 | find . -name '*.pyc' -exec rm -f {} + 54 | find . -name '*.pyo' -exec rm -f {} + 55 | find . -name '*~' -exec rm -f {} + 56 | find . -name '__pycache__' -exec rm -fr {} + 57 | 58 | clean-test: ## remove test and coverage artifacts 59 | rm -fr .tox/ 60 | rm -f .coverage 61 | rm -fr htmlcov/ 62 | rm -fr .pytest_cache 63 | 64 | coverage: ## check code coverage quickly with the default Python 65 | $(PYTHON) -m coverage run --source src -m pytest 66 | $(PYTHON) -m coverage report -m 67 | $(PYTHON) -m coverage html 68 | $(BROWSER) htmlcov/index.html 69 | 70 | deps: ## Install development dependencies 71 | $(PYTHON) -m pip install -r requirements_dev.txt 72 | $(PYTHON) setup.py develop 73 | 74 | dist: clean ## builds source and wheel package 75 | $(PYTHON) setup.py sdist 76 | $(PYTHON) setup.py bdist_wheel 77 | ls -l dist 78 | 79 | docs: ## generate Sphinx HTML documentation, including API docs 80 | rm -f docs/qiskit_qir.rst 81 | rm -f docs/modules.rst 82 | sphinx-apidoc -o docs/ src 83 | $(MAKE) -C docs clean 84 | $(MAKE) -C docs html 85 | $(BROWSER) docs/_build/html/index.html 86 | 87 | install: clean ## install the package to the active Python's site-packages 88 | $(PYTHON) setup.py install 89 | 90 | lint/flake8: ## check style with flake8 91 | $(PYTHON) -m flake8 src tests 92 | 93 | lint/black: ## check style with black 94 | $(PYTHON) -m black --check src tests 95 | 96 | lint: lint/flake8 lint/black ## check style 97 | 98 | release: dist ## package and upload a release 99 | $(PYTHON) -m twine upload dist/* 100 | 101 | servedocs: docs ## compile the docs watching for changes 102 | $(PYTHON) -m watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 103 | 104 | test: ## run tests quickly with the default Python 105 | $(PYTHON) -m pytest 106 | 107 | test-all: ## run tests on every Python version with tox 108 | $(PYTHON) -m tox 109 | 110 | test-release: dist ## package and upload a release 111 | twine upload --repository testpypi dist/* 112 | 113 | venv: $(VENV_PYTHON) ## Creates the python virtual environment 114 | $(VENV_PYTHON) --version 115 | $(VENV_PYTHON) -m pip --version 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATION NOTICE 2 | 3 | **This repository is deprecated.** 4 | 5 | Functionality for converting Qiskit to QIR is now part of the Modern QDK's [Qiskit interop](https://github.com/microsoft/qsharp/wiki/Qiskit-Interop). 6 | 7 | For the Modern QDK repository, please visit [Microsoft/qsharp](https://github.com/microsoft/qsharp). 8 | 9 | You can also try out the Modern QDK in VS Code for Web at [vscode.dev/quantum](https://vscode.dev/quantum). 10 | 11 | For more information about the Modern QDK and Azure Quantum, visit [https://aka.ms/AQ/Documentation](https://aka.ms/AQ/Documentation). 12 | 13 | # qiskit-qir 14 | 15 | Qiskit to QIR translator. 16 | 17 | ## Example 18 | 19 | ```python 20 | from qiskit import QuantumCircuit 21 | from qiskit_qir import to_qir_module 22 | 23 | circuit = QuantumCircuit(3, 3, name="my-circuit") 24 | circuit.h(0) 25 | circuit.cx(0, 1) 26 | circuit.cx(1, 2) 27 | circuit.measure([0,1,2], [0, 1, 2]) 28 | 29 | module, entry_points = to_qir_module(circuit) 30 | bitcode = module.bitcode 31 | ir = str(module) 32 | ``` 33 | 34 | ## Installation 35 | 36 | Install `qiskit-qir` with `pip`: 37 | 38 | ```bash 39 | pip install qiskit-qir 40 | ``` 41 | > Note: this will automatically install PyQIR if needed. 42 | 43 | ## Development 44 | 45 | ### Install from source 46 | 47 | To install the package from source, clone the repo onto your machine, browse to the root directory and run 48 | 49 | ```bash 50 | pip install -e . 51 | ``` 52 | 53 | ### Tests 54 | 55 | First, install the development dependencies using 56 | 57 | ```bash 58 | pip install -r requirements_dev.txt 59 | ``` 60 | 61 | To run the tests in your local environment, run 62 | 63 | ```bash 64 | make test 65 | ``` 66 | 67 | To run the tests in virtual environments on supported Python versions, run 68 | 69 | ```bash 70 | make test-all 71 | ``` 72 | 73 | ### Docs 74 | 75 | To build the docs using Sphinx, run 76 | 77 | ```bash 78 | make docs 79 | ``` 80 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = src 8 | SOURCEDIR = . 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 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # qiskit_qir documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another 16 | # directory, add these directories to sys.path here. If the directory is 17 | # relative to the documentation root, use os.path.abspath to make it 18 | # absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | 23 | sys.path.insert(0, os.path.abspath("..")) 24 | 25 | import qiskit_qir 26 | 27 | # -- General configuration --------------------------------------------- 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 35 | extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ["_templates"] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = ".rst" 45 | 46 | # The master toctree document. 47 | master_doc = "index" 48 | 49 | # General information about the project. 50 | project = "qiskit-qir" 51 | copyright = "2022, Microsoft" 52 | author = "Microsoft" 53 | 54 | # The version info for the project you're documenting, acts as replacement 55 | # for |version| and |release|, also used in various other places throughout 56 | # the built documents. 57 | # 58 | # The short X.Y version. 59 | version = qiskit_qir.__version__ 60 | # The full version, including alpha/beta/rc tags. 61 | release = qiskit_qir.__version__ 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This patterns also effect to html_static_path and html_extra_path 73 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = "sphinx" 77 | 78 | # If true, `todo` and `todoList` produce output, else they produce nothing. 79 | todo_include_todos = False 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 | # 87 | html_theme = "alabaster" 88 | 89 | # Theme options are theme-specific and customize the look and feel of a 90 | # theme further. For a list of options available for each theme, see the 91 | # documentation. 92 | # 93 | # html_theme_options = {} 94 | 95 | # Add any paths that contain custom static files (such as style sheets) here, 96 | # relative to this directory. They are copied after the builtin static files, 97 | # so a file named "default.css" will overwrite the builtin "default.css". 98 | html_static_path = ["_static"] 99 | 100 | 101 | # -- Options for HTMLHelp output --------------------------------------- 102 | 103 | # Output file base name for HTML help builder. 104 | htmlhelp_basename = "qiskit_qirdoc" 105 | 106 | 107 | # -- Options for LaTeX output ------------------------------------------ 108 | 109 | latex_elements = { 110 | # The paper size ('letterpaper' or 'a4paper'). 111 | # 112 | # 'papersize': 'letterpaper', 113 | # The font size ('10pt', '11pt' or '12pt'). 114 | # 115 | # 'pointsize': '10pt', 116 | # Additional stuff for the LaTeX preamble. 117 | # 118 | # 'preamble': '', 119 | # Latex figure (float) alignment 120 | # 121 | # 'figure_align': 'htbp', 122 | } 123 | 124 | # Grouping the document tree into LaTeX files. List of tuples 125 | # (source start file, target name, title, author, documentclass 126 | # [howto, manual, or own class]). 127 | latex_documents = [ 128 | (master_doc, "qiskit_qir.tex", "qiskit-qir Documentation", "Microsoft", "manual"), 129 | ] 130 | 131 | 132 | # -- Options for manual page output ------------------------------------ 133 | 134 | # One entry per manual page. List of tuples 135 | # (source start file, name, description, authors, manual section). 136 | man_pages = [(master_doc, "qiskit_qir", "qiskit-qir Documentation", [author], 1)] 137 | 138 | 139 | # -- Options for Texinfo output ---------------------------------------- 140 | 141 | # Grouping the document tree into Texinfo files. List of tuples 142 | # (source start file, target name, title, author, 143 | # dir menu entry, description, category) 144 | texinfo_documents = [ 145 | ( 146 | master_doc, 147 | "qiskit_qir", 148 | "qiskit-qir Documentation", 149 | author, 150 | "qiskit_qir", 151 | "One line description of project.", 152 | "Miscellaneous", 153 | ), 154 | ] 155 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to qiskit-qir's documentation! 2 | ====================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | readme 9 | installation 10 | usage 11 | modules 12 | contributing 13 | history 14 | 15 | Indices and tables 16 | ================== 17 | * :ref:`genindex` 18 | * :ref:`modindex` 19 | * :ref:`search` 20 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install qiskit-qir, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install qiskit-qir 16 | 17 | This is the preferred method to install qiskit-qir, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | 25 | 26 | From sources 27 | ------------ 28 | 29 | The sources for qiskit-qir can be downloaded from the `Github repo`_. 30 | 31 | You can either clone the public repository: 32 | 33 | .. code-block:: console 34 | 35 | $ git clone git://github.com/microsoft/qiskit-qir 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OJL https://github.com/microsoft/qiskit-qir/tarball/master 42 | 43 | Once you have a copy of the source, you can install it with: 44 | 45 | .. code-block:: console 46 | 47 | $ python setup.py install 48 | 49 | 50 | .. _Github repo: https://github.com/microsoft/qiskit-qir 51 | .. _tarball: https://github.com/microsoft/qiskit-qir/tarball/main 52 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=qiskit_qir 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | qiskit-qir 3 | ========== 4 | 5 | 6 | .. image:: https://img.shields.io/pypi/v/qiskit_qir.svg 7 | :target: https://pypi.python.org/pypi/qiskit_qir 8 | 9 | .. image:: https://img.shields.io/travis/guenp/qiskit_qir.svg 10 | :target: https://travis-ci.com/guenp/qiskit_qir 11 | 12 | .. image:: https://readthedocs.org/projects/qiskit-qir/badge/?version=latest 13 | :target: https://qiskit-qir.readthedocs.io/en/latest/?version=latest 14 | :alt: Documentation Status 15 | 16 | 17 | 18 | 19 | Qiskit to QIR translator 20 | 21 | 22 | * Free software: MIT license 23 | * Documentation: https://qiskit-qir.readthedocs.io. 24 | 25 | 26 | Features 27 | -------- 28 | 29 | * TODO 30 | 31 | Credits 32 | ------- 33 | 34 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 35 | 36 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 37 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 38 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use qiskit-qir in a project:: 6 | 7 | import qiskit_qir 8 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel>=0.38.1" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | wheel>=0.42 2 | watchdog==3.0.0 3 | flake8==6.1.0 4 | tox==4.12.1 5 | coverage==7.4.1 6 | Sphinx==7.1.2 7 | jinja2==3.1.4 8 | twine==4.0.2 9 | pytest==7.4.4 10 | black==24.3.0 11 | pyqir==0.10.0 12 | qiskit>=1.0.0 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = qiskit-qir 3 | version = 0.5.0 4 | author = Microsoft 5 | author_email = que-contacts@microsoft.com 6 | description = Qiskit to QIR translator 7 | long_description = file: README.md 8 | long_description_content_type = text/markdown 9 | url = https://github.com/microsoft/qiskit-qir 10 | project_urls = 11 | Bug Tracker = https://github.com/microsoft/qiskit-qir 12 | classifiers = 13 | Development Status :: 3 - Alpha 14 | Intended Audience :: Developers 15 | License :: OSI Approved :: MIT License 16 | Natural Language :: English 17 | Programming Language :: Python :: 3 18 | Programming Language :: Python :: 3.8 19 | Programming Language :: Python :: 3.9 20 | Programming Language :: Python :: 3.10 21 | Programming Language :: Python :: 3.11 22 | Programming Language :: Python :: 3.12 23 | Operating System :: Microsoft 24 | Operating System :: MacOS 25 | Operating System :: Unix 26 | 27 | [options] 28 | package_dir = 29 | = src 30 | packages = find: 31 | python_requires = >=3.8 32 | install_requires = 33 | qiskit>=1.0.0,<2.0 34 | pyqir>=0.10.0,<0.11.0 35 | 36 | [options.extras_require] 37 | test = pytest 38 | 39 | [options.packages.find] 40 | where = src 41 | 42 | [bdist_wheel] 43 | universal = 1 44 | 45 | [flake8] 46 | exclude = docs 47 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup() 4 | -------------------------------------------------------------------------------- /src/qiskit_qir/__init__.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | __author__ = """Microsoft Corporation""" 6 | __email__ = "que-contacts@microsoft.com" 7 | __version__ = "0.5.0" 8 | 9 | from qiskit_qir.translate import to_qir_module 10 | -------------------------------------------------------------------------------- /src/qiskit_qir/capability.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | from enum import Flag, auto 6 | import os 7 | from typing import Dict, List, Union 8 | from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister 9 | from qiskit.circuit import Qubit, Clbit 10 | from qiskit.circuit.instruction import Instruction 11 | 12 | 13 | class Capability(Flag): 14 | NONE = 0 15 | CONDITIONAL_BRANCHING_ON_RESULT = auto() 16 | QUBIT_USE_AFTER_MEASUREMENT = auto() 17 | ALL = CONDITIONAL_BRANCHING_ON_RESULT | QUBIT_USE_AFTER_MEASUREMENT 18 | 19 | 20 | class CapabilityError(Exception): 21 | """Base class for profile validation exceptions""" 22 | 23 | def _get_bit_labels( 24 | self, 25 | circuit: QuantumCircuit, 26 | ) -> Dict[Union[Qubit, Clbit], str]: 27 | register_names: Dict[str, Union[QuantumRegister, ClassicalRegister]] = {} 28 | for registers in (circuit.qregs, circuit.cregs): 29 | for register in registers: 30 | register_names[register.name] = register 31 | bit_labels: Dict[Union[Qubit, Clbit], str] = { 32 | bit: "%s[%d]" % (name, idx) 33 | for name, register in register_names.items() 34 | for (idx, bit) in enumerate(register) 35 | } 36 | return bit_labels 37 | 38 | def _get_instruction_string( 39 | self, 40 | bit_labels: Dict[Union[Qubit, Clbit], str], 41 | instruction: Instruction, 42 | qargs: List[Qubit], 43 | cargs: List[Clbit], 44 | ): 45 | gate_params = ",".join(["param(%s)" % bit_labels[c] for c in cargs]) 46 | qubit_params = ",".join(["%s" % bit_labels[q] for q in qargs]) 47 | instruction_name = instruction.name 48 | if instruction.condition is not None: 49 | # condition should be a 50 | # - tuple (ClassicalRegister, int) 51 | # - tuple (Clbit, bool) 52 | # - tuple (Clbit, int) 53 | if isinstance(instruction.condition[0], Clbit): 54 | bit: Clbit = instruction.condition[0] 55 | value: Union[int, bool] = instruction.condition[1] 56 | instruction_name = "if(%s[%d] == %s) %s" % ( 57 | bit._register.name, 58 | bit._index, 59 | value, 60 | instruction_name, 61 | ) 62 | else: 63 | register: ClassicalRegister = instruction.condition[0] 64 | value: int = instruction.condition[1] 65 | instruction_name = "if(%s == %d) %s" % ( 66 | register._name, 67 | value, 68 | instruction_name, 69 | ) 70 | 71 | if gate_params: 72 | return f"{instruction_name}({gate_params}) {qubit_params}" 73 | else: 74 | return f"{instruction_name} {qubit_params}" 75 | 76 | 77 | class ConditionalBranchingOnResultError(CapabilityError): 78 | def __init__( 79 | self, 80 | circuit: QuantumCircuit, 81 | instruction: Instruction, 82 | qargs: List[Qubit], 83 | cargs: List[Clbit], 84 | profile: str, 85 | ): 86 | bit_labels: Dict[Union[Qubit, Clbit], str] = self._get_bit_labels(circuit) 87 | instruction_string = self._get_instruction_string( 88 | bit_labels, instruction, qargs, cargs 89 | ) 90 | self.instruction_string = instruction_string 91 | self.msg_suffix = "Support for branching based on measurement requires Capability.CONDITIONAL_BRANCHING_ON_RESULT" 92 | self.msg = f"Attempted to branch on register value.{os.linesep}Instruction: {instruction_string}{os.linesep}{self.msg_suffix}" 93 | CapabilityError.__init__(self, self.msg) 94 | self.instruction = instruction 95 | self.qargs = qargs 96 | self.cargs = cargs 97 | self.profile = profile 98 | 99 | 100 | class QubitUseAfterMeasurementError(CapabilityError): 101 | def __init__( 102 | self, 103 | circuit: QuantumCircuit, 104 | instruction: Instruction, 105 | qargs: List[Qubit], 106 | cargs: List[Clbit], 107 | profile: str, 108 | ): 109 | bit_labels: Dict[Union[Qubit, Clbit], str] = self._get_bit_labels(circuit) 110 | instruction_string = self._get_instruction_string( 111 | bit_labels, instruction, qargs, cargs 112 | ) 113 | self.instruction_string = instruction_string 114 | self.msg_suffix = ( 115 | "Support for qubit reuse requires Capability.QUBIT_USE_AFTER_MEASUREMENT" 116 | ) 117 | self.msg = f"Qubit was used after being measured.{os.linesep}Instruction: {instruction_string}{os.linesep}{self.msg_suffix}" 118 | CapabilityError.__init__(self, self.msg) 119 | self.instruction = instruction 120 | self.qargs = qargs 121 | self.cargs = cargs 122 | self.profile = profile 123 | -------------------------------------------------------------------------------- /src/qiskit_qir/elements.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | from typing import List, Optional, Union 6 | from pyqir import Module, Context 7 | from qiskit import ClassicalRegister, QuantumRegister 8 | from qiskit.circuit.bit import Bit 9 | from qiskit.circuit.quantumcircuit import QuantumCircuit, Instruction 10 | from abc import ABCMeta, abstractmethod 11 | 12 | 13 | class _QuantumCircuitElement(metaclass=ABCMeta): 14 | @classmethod 15 | def from_element_list(cls, elements): 16 | return [cls(elem) for elem in elements] 17 | 18 | @abstractmethod 19 | def accept(self, visitor): 20 | pass 21 | 22 | 23 | class _Register(_QuantumCircuitElement): 24 | def __init__(self, register: Union[QuantumRegister, ClassicalRegister]): 25 | self._register: Union[QuantumRegister, ClassicalRegister] = register 26 | 27 | def accept(self, visitor): 28 | visitor.visit_register(self._register) 29 | 30 | 31 | class _Instruction(_QuantumCircuitElement): 32 | def __init__(self, instruction: Instruction, qargs: List[Bit], cargs: List[Bit]): 33 | self._instruction: Instruction = instruction 34 | self._qargs = qargs 35 | self._cargs = cargs 36 | 37 | def accept(self, visitor): 38 | visitor.visit_instruction(self._instruction, self._qargs, self._cargs) 39 | 40 | 41 | class QiskitModule: 42 | def __init__( 43 | self, 44 | circuit: QuantumCircuit, 45 | name: str, 46 | module: Module, 47 | num_qubits: int, 48 | num_clbits: int, 49 | reg_sizes: List[int], 50 | elements: List[_QuantumCircuitElement], 51 | ): 52 | self._circuit = circuit 53 | self._name = name 54 | self._module = module 55 | self._elements = elements 56 | self._num_qubits = num_qubits 57 | self._num_clbits = num_clbits 58 | self.reg_sizes = reg_sizes 59 | 60 | @property 61 | def circuit(self) -> QuantumCircuit: 62 | return self._circuit 63 | 64 | @property 65 | def name(self) -> str: 66 | return self._name 67 | 68 | @property 69 | def module(self) -> Module: 70 | return self._module 71 | 72 | @property 73 | def num_qubits(self) -> int: 74 | return self._num_qubits 75 | 76 | @property 77 | def num_clbits(self) -> int: 78 | return self._num_clbits 79 | 80 | @classmethod 81 | def from_quantum_circuit( 82 | cls, circuit: QuantumCircuit, module: Optional[Module] = None 83 | ) -> "QiskitModule": 84 | """Create a new QiskitModule from a qiskit.QuantumCircuit object.""" 85 | elements: List[_QuantumCircuitElement] = [] 86 | reg_sizes = [len(creg) for creg in circuit.cregs] 87 | 88 | # Registers 89 | elements.extend(_Register.from_element_list(circuit.qregs)) 90 | elements.extend(_Register.from_element_list(circuit.cregs)) 91 | 92 | # Instructions 93 | for instruction, qargs, cargs in circuit._data: 94 | elements.append(_Instruction(instruction, qargs, cargs)) 95 | 96 | if module is None: 97 | module = Module(Context(), circuit.name) 98 | return cls( 99 | circuit=circuit, 100 | name=circuit.name, 101 | module=module, 102 | num_qubits=circuit.num_qubits, 103 | num_clbits=circuit.num_clbits, 104 | reg_sizes=reg_sizes, 105 | elements=elements, 106 | ) 107 | 108 | def accept(self, visitor): 109 | visitor.visit_qiskit_module(self) 110 | for element in self._elements: 111 | element.accept(visitor) 112 | visitor.record_output(self) 113 | visitor.finalize() 114 | -------------------------------------------------------------------------------- /src/qiskit_qir/translate.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | from qiskit_qir.visitor import BasicQisVisitor 6 | from qiskit.circuit.quantumcircuit import QuantumCircuit 7 | from typing import List, Tuple, Union 8 | from pyqir import Context, Module, qir_module 9 | from qiskit_qir.elements import QiskitModule 10 | 11 | 12 | def to_qir_module( 13 | circuits: Union[QuantumCircuit, List[QuantumCircuit]], 14 | profile: str = "AdaptiveExecution", 15 | **kwargs 16 | ) -> Tuple[Module, List[str]]: 17 | r"""Converts the Qiskit QuantumCircuit(s) to a QIR Module with 18 | its entry point names. 19 | 20 | :param circuits: 21 | Qiskit circuit(s) to be converted to QIR 22 | :type circuit: ``Union[QuantumCircuit, List[QuantumCircuit]]`` 23 | :param profile: 24 | The target profile for capability verification 25 | :type profile: ``str`` 26 | :param \**kwargs: 27 | See below 28 | :returns: 29 | Tuple containing the the QIR ``pyqir.Module`` representation of the input and 30 | the list of used entry point names generated from the input. 31 | 32 | :Keyword Arguments: 33 | * *record_output* (``bool``) -- 34 | Whether to record output calls for registers, default `True` 35 | * *emit_barrier_calls* (``bool``) -- 36 | Whether to emit barrier calls in the QIR, default `False` 37 | """ 38 | 39 | name = "batch" 40 | if isinstance(circuits, QuantumCircuit): 41 | name = circuits.name 42 | circuits = [circuits] 43 | elif isinstance(circuits, list): 44 | for value in circuits: 45 | if not isinstance(value, QuantumCircuit): 46 | raise ValueError( 47 | "Input must be Union[QuantumCircuit, List[QuantumCircuit]]" 48 | ) 49 | else: 50 | raise ValueError("Input must be Union[QuantumCircuit, List[QuantumCircuit]]") 51 | 52 | if len(circuits) == 0: 53 | raise ValueError("No QuantumCircuits provided") 54 | 55 | llvm_module = qir_module(Context(), name) 56 | entry_points = [] 57 | for circuit in circuits: 58 | module = QiskitModule.from_quantum_circuit(circuit, llvm_module) 59 | visitor = BasicQisVisitor(profile, **kwargs) 60 | module.accept(visitor) 61 | entry_points.append(visitor.entry_point) 62 | err = llvm_module.verify() 63 | if err is not None: 64 | raise Exception(err) 65 | return (llvm_module, entry_points) 66 | -------------------------------------------------------------------------------- /src/qiskit_qir/visitor.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | from io import UnsupportedOperation 6 | import logging 7 | from abc import ABCMeta, abstractmethod 8 | from qiskit import ClassicalRegister, QuantumRegister 9 | from qiskit.circuit import Qubit, Clbit 10 | from qiskit.circuit.instruction import Instruction 11 | from qiskit.circuit.bit import Bit 12 | import pyqir.qis as qis 13 | import pyqir.rt as rt 14 | import pyqir 15 | from pyqir import ( 16 | BasicBlock, 17 | Builder, 18 | Constant, 19 | Function, 20 | FunctionType, 21 | IntType, 22 | Linkage, 23 | Module, 24 | PointerType, 25 | const, 26 | entry_point, 27 | qubit_id, 28 | ) 29 | from typing import List, Union 30 | 31 | from qiskit_qir.capability import ( 32 | Capability, 33 | ConditionalBranchingOnResultError, 34 | QubitUseAfterMeasurementError, 35 | ) 36 | from qiskit_qir.elements import QiskitModule 37 | 38 | _log = logging.getLogger(name=__name__) 39 | 40 | # This list cannot change as existing clients hardcoded to it 41 | # when it wasn't designed to be externally used. 42 | # To work around this we are using an additional list to replace 43 | # this list which contains the instructions that we can process. 44 | # This following three variables can be removed in a future 45 | # release after dependency version restrictions have been applied. 46 | SUPPORTED_INSTRUCTIONS = [ 47 | "barrier", 48 | "delay", 49 | "measure", 50 | "m", 51 | "cx", 52 | "cz", 53 | "h", 54 | "reset", 55 | "rx", 56 | "ry", 57 | "rz", 58 | "s", 59 | "sdg", 60 | "t", 61 | "tdg", 62 | "x", 63 | "y", 64 | "z", 65 | "id", 66 | ] 67 | 68 | _QUANTUM_INSTRUCTIONS = [ 69 | "barrier", 70 | "ccx", 71 | "cx", 72 | "cz", 73 | "h", 74 | "id", 75 | "m", 76 | "measure", 77 | "reset", 78 | "rx", 79 | "ry", 80 | "rz", 81 | "s", 82 | "sdg", 83 | "swap", 84 | "t", 85 | "tdg", 86 | "x", 87 | "y", 88 | "z", 89 | ] 90 | 91 | _NOOP_INSTRUCTIONS = ["delay"] 92 | 93 | _SUPPORTED_INSTRUCTIONS = _QUANTUM_INSTRUCTIONS + _NOOP_INSTRUCTIONS 94 | 95 | 96 | class QuantumCircuitElementVisitor(metaclass=ABCMeta): 97 | @abstractmethod 98 | def visit_register(self, register): 99 | raise NotImplementedError 100 | 101 | @abstractmethod 102 | def visit_instruction(self, instruction): 103 | raise NotImplementedError 104 | 105 | 106 | class BasicQisVisitor(QuantumCircuitElementVisitor): 107 | def __init__(self, profile: str = "AdaptiveExecution", **kwargs): 108 | self._module = None 109 | self._qiskitModule: QiskitModule | None = None 110 | self._builder = None 111 | self._entry_point = None 112 | self._qubit_labels = {} 113 | self._clbit_labels = {} 114 | self._profile = profile 115 | self._capabilities = self._map_profile_to_capabilities(profile) 116 | self._measured_qubits = {} 117 | self._emit_barrier_calls = kwargs.get("emit_barrier_calls", False) 118 | self._record_output = kwargs.get("record_output", True) 119 | 120 | def visit_qiskit_module(self, module: QiskitModule): 121 | _log.debug( 122 | f"Visiting Qiskit module '{module.name}' ({module.num_qubits}, {module.num_clbits})" 123 | ) 124 | self._module = module.module 125 | self._qiskitModule = module 126 | context = self._module.context 127 | entry = entry_point( 128 | self._module, module.name, module.num_qubits, module.num_clbits 129 | ) 130 | 131 | self._entry_point = entry.name 132 | self._builder = Builder(context) 133 | self._builder.insert_at_end(BasicBlock(context, "entry", entry)) 134 | 135 | i8p = PointerType(IntType(context, 8)) 136 | nullptr = Constant.null(i8p) 137 | rt.initialize(self._builder, nullptr) 138 | 139 | @property 140 | def entry_point(self) -> str: 141 | return self._entry_point 142 | 143 | def finalize(self): 144 | self._builder.ret(None) 145 | 146 | def record_output(self, module: QiskitModule): 147 | if self._record_output == False: 148 | return 149 | 150 | i8p = PointerType(IntType(self._module.context, 8)) 151 | 152 | # qiskit inverts the ordering of the results within each register 153 | # but keeps the overall register ordering 154 | # here we logically loop from n-1 to 0, decrementing in order to 155 | # invert the register output. The second parameter is an exclusive 156 | # range so we need to go to -1 instead of 0 157 | logical_id_base = 0 158 | for size in module.reg_sizes: 159 | rt.array_record_output( 160 | self._builder, 161 | const(IntType(self._module.context, 64), size), 162 | Constant.null(i8p), 163 | ) 164 | for index in range(size - 1, -1, -1): 165 | result_ref = pyqir.result(self._module.context, logical_id_base + index) 166 | rt.result_record_output(self._builder, result_ref, Constant.null(i8p)) 167 | logical_id_base += size 168 | 169 | def visit_register(self, register): 170 | _log.debug(f"Visiting register '{register.name}'") 171 | if isinstance(register, QuantumRegister): 172 | self._qubit_labels.update( 173 | {bit: n + len(self._qubit_labels) for n, bit in enumerate(register)} 174 | ) 175 | _log.debug( 176 | f"Added labels for qubits {[bit for n, bit in enumerate(register)]}" 177 | ) 178 | elif isinstance(register, ClassicalRegister): 179 | self._clbit_labels.update( 180 | {bit: n + len(self._clbit_labels) for n, bit in enumerate(register)} 181 | ) 182 | else: 183 | raise ValueError(f"Register of type {type(register)} not supported.") 184 | 185 | def process_composite_instruction( 186 | self, instruction: Instruction, qargs: List[Qubit], cargs: List[Clbit] 187 | ): 188 | subcircuit = instruction.definition 189 | _log.debug( 190 | f"Processing composite instruction {instruction.name} with qubits {qargs}" 191 | ) 192 | if len(qargs) != subcircuit.num_qubits: 193 | raise ValueError( 194 | f"Composite instruction {instruction.name} called with the wrong number of qubits; \ 195 | {subcircuit.num_qubits} expected, {len(qargs)} provided" 196 | ) 197 | if len(cargs) != subcircuit.num_clbits: 198 | raise ValueError( 199 | f"Composite instruction {instruction.name} called with the wrong number of classical bits; \ 200 | {subcircuit.num_clbits} expected, {len(cargs)} provided" 201 | ) 202 | for inst, i_qargs, i_cargs in subcircuit.data: 203 | mapped_qbits = [qargs[subcircuit.qubits.index(i)] for i in i_qargs] 204 | mapped_clbits = [cargs[subcircuit.clbits.index(i)] for i in i_cargs] 205 | _log.debug( 206 | f"Processing sub-instruction {inst.name} with mapped qubits {mapped_qbits}" 207 | ) 208 | self.visit_instruction(inst, mapped_qbits, mapped_clbits) 209 | 210 | def visit_instruction( 211 | self, 212 | instruction: Instruction, 213 | qargs: List[Bit], 214 | cargs: List[Bit], 215 | skip_condition=False, 216 | ): 217 | qlabels = [self._qubit_labels.get(bit) for bit in qargs] 218 | clabels = [self._clbit_labels.get(bit) for bit in cargs] 219 | qubits = [pyqir.qubit(self._module.context, n) for n in qlabels] 220 | results = [pyqir.result(self._module.context, n) for n in clabels] 221 | 222 | if ( 223 | instruction.condition is not None 224 | ) and not self._capabilities & Capability.CONDITIONAL_BRANCHING_ON_RESULT: 225 | raise ConditionalBranchingOnResultError( 226 | self._qiskitModule.circuit, instruction, qargs, cargs, self._profile 227 | ) 228 | 229 | labels = ", ".join([str(l) for l in qlabels + clabels]) 230 | if instruction.condition is None or skip_condition: 231 | _log.debug(f"Visiting instruction '{instruction.name}' ({labels})") 232 | 233 | if instruction.condition is not None and skip_condition is False: 234 | _log.debug( 235 | f"Visiting condition for instruction '{instruction.name}' ({labels})" 236 | ) 237 | 238 | if isinstance(instruction.condition[0], Clbit): 239 | bit_label = self._clbit_labels.get(instruction.condition[0]) 240 | conditions = [pyqir.result(self._module.context, bit_label)] 241 | else: 242 | conditions = [ 243 | pyqir.result(self._module.context, self._clbit_labels.get(bit)) 244 | for bit in instruction.condition[0] 245 | ] 246 | 247 | # Convert value into a bitstring of the same length as classical register 248 | # condition should be a 249 | # - tuple (ClassicalRegister, int) 250 | # - tuple (Clbit, bool) 251 | # - tuple (Clbit, int) 252 | if isinstance(instruction.condition[0], Clbit): 253 | bit: Clbit = instruction.condition[0] 254 | value: Union[int, bool] = instruction.condition[1] 255 | if value: 256 | values = "1" 257 | else: 258 | values = "0" 259 | else: 260 | register: ClassicalRegister = instruction.condition[0] 261 | value: int = instruction.condition[1] 262 | values = format(value, f"0{register.size}b") 263 | 264 | # Add branches recursively for each bit in the bitstring 265 | def __visit(): 266 | self.visit_instruction(instruction, qargs, cargs, skip_condition=True) 267 | 268 | def _branch(conditions_values): 269 | try: 270 | cond, val = next(conditions_values) 271 | 272 | def __branch(): 273 | qis.if_result( 274 | self._builder, 275 | cond, 276 | one=_branch(conditions_values) if val == "1" else None, 277 | zero=_branch(conditions_values) if val == "0" else None, 278 | ) 279 | 280 | except StopIteration: 281 | return __visit 282 | else: 283 | return __branch 284 | 285 | if len(conditions) < len(values): 286 | raise ValueError( 287 | f"Value {value} is larger than register width {len(conditions)}." 288 | ) 289 | 290 | # qiskit has the most significant bit on the right, so we 291 | # must reverse the bit array for comparisons. 292 | _branch(zip(conditions, values[::-1]))() 293 | elif ( 294 | "measure" == instruction.name 295 | or "m" == instruction.name 296 | or "mz" == instruction.name 297 | ): 298 | for qubit, result in zip(qubits, results): 299 | self._measured_qubits[qubit_id(qubit)] = True 300 | qis.mz(self._builder, qubit, result) 301 | else: 302 | if not self._capabilities & Capability.QUBIT_USE_AFTER_MEASUREMENT: 303 | # If we have a supported instruction, apply the capability 304 | # check. If we have a composite instruction then it will call 305 | # back into this function with a supported name and we'll 306 | # verify at that time 307 | if instruction.name in _SUPPORTED_INSTRUCTIONS: 308 | if any(map(self._measured_qubits.get, map(qubit_id, qubits))): 309 | raise QubitUseAfterMeasurementError( 310 | self._qiskitModule.circuit, 311 | instruction, 312 | qargs, 313 | cargs, 314 | self._profile, 315 | ) 316 | if "barrier" == instruction.name: 317 | if self._emit_barrier_calls: 318 | qis.barrier(self._builder) 319 | elif "delay" == instruction.name: 320 | pass 321 | elif "swap" == instruction.name: 322 | qis.swap(self._builder, *qubits) 323 | elif "ccx" == instruction.name: 324 | qis.ccx(self._builder, *qubits) 325 | elif "cx" == instruction.name: 326 | qis.cx(self._builder, *qubits) 327 | elif "cz" == instruction.name: 328 | qis.cz(self._builder, *qubits) 329 | elif "h" == instruction.name: 330 | qis.h(self._builder, *qubits) 331 | elif "reset" == instruction.name: 332 | qis.reset(self._builder, qubits[0]) 333 | elif "rx" == instruction.name: 334 | qis.rx(self._builder, *instruction.params, *qubits) 335 | elif "ry" == instruction.name: 336 | qis.ry(self._builder, *instruction.params, *qubits) 337 | elif "rz" == instruction.name: 338 | qis.rz(self._builder, *instruction.params, *qubits) 339 | elif "s" == instruction.name: 340 | qis.s(self._builder, *qubits) 341 | elif "sdg" == instruction.name: 342 | qis.s_adj(self._builder, *qubits) 343 | elif "t" == instruction.name: 344 | qis.t(self._builder, *qubits) 345 | elif "tdg" == instruction.name: 346 | qis.t_adj(self._builder, *qubits) 347 | elif "x" == instruction.name: 348 | qis.x(self._builder, *qubits) 349 | elif "y" == instruction.name: 350 | qis.y(self._builder, *qubits) 351 | elif "z" == instruction.name: 352 | qis.z(self._builder, *qubits) 353 | elif "id" == instruction.name: 354 | # See: https://github.com/qir-alliance/pyqir/issues/74 355 | qubit = pyqir.qubit(self._module.context, qubit_id(*qubits)) 356 | qis.x(self._builder, qubit) 357 | qis.x(self._builder, qubit) 358 | elif instruction.definition: 359 | _log.debug( 360 | f"About to process composite instruction {instruction.name} with qubits {qargs}" 361 | ) 362 | self.process_composite_instruction(instruction, qargs, cargs) 363 | else: 364 | raise ValueError( 365 | f"Gate {instruction.name} is not supported. \ 366 | Please transpile using the list of supported gates: {_SUPPORTED_INSTRUCTIONS}." 367 | ) 368 | 369 | def ir(self) -> str: 370 | return str(self._module) 371 | 372 | def bitcode(self) -> bytes: 373 | return self._module.bitcode() 374 | 375 | def _map_profile_to_capabilities(self, profile: str): 376 | value = profile.strip().lower() 377 | if "BasicExecution".lower() == value: 378 | return Capability.NONE 379 | elif "AdaptiveExecution".lower() == value: 380 | return Capability.ALL 381 | else: 382 | raise UnsupportedOperation( 383 | f"The supplied profile is not supported: {profile}." 384 | ) 385 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | 6 | from test_circuits import * 7 | from test_circuits.basic_gates import * 8 | from test_circuits.control_flow_circuits import * 9 | 10 | collect_ignore = ["setup.py"] 11 | -------------------------------------------------------------------------------- /tests/resources/test_single_clbit_variations_falsy.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_single_clbit_variations_falsy' 2 | source_filename = "test_single_clbit_variations" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | define void @test_single_clbit_variations() #0 { 8 | entry: 9 | call void @__quantum__rt__initialize(i8* null) 10 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 11 | %0 = call i1 @__quantum__qis__read_result__body(%Result* null) 12 | br i1 %0, label %then, label %else 13 | 14 | then: ; preds = %entry 15 | br label %continue 16 | 17 | else: ; preds = %entry 18 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 19 | br label %continue 20 | 21 | continue: ; preds = %else, %then 22 | ret void 23 | } 24 | 25 | declare void @__quantum__rt__initialize(i8*) 26 | 27 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 28 | 29 | declare i1 @__quantum__qis__read_result__body(%Result*) 30 | 31 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" } 32 | attributes #1 = { "irreversible" } 33 | 34 | !llvm.module.flags = !{!0, !1, !2, !3} 35 | 36 | !0 = !{i32 1, !"qir_major_version", i32 1} 37 | !1 = !{i32 7, !"qir_minor_version", i32 0} 38 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 39 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 40 | -------------------------------------------------------------------------------- /tests/resources/test_single_clbit_variations_truthy.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_single_clbit_variations_truthy' 2 | source_filename = "test_single_clbit_variations" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | define void @test_single_clbit_variations() #0 { 8 | entry: 9 | call void @__quantum__rt__initialize(i8* null) 10 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 11 | %0 = call i1 @__quantum__qis__read_result__body(%Result* null) 12 | br i1 %0, label %then, label %else 13 | 14 | then: ; preds = %entry 15 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 16 | br label %continue 17 | 18 | else: ; preds = %entry 19 | br label %continue 20 | 21 | continue: ; preds = %else, %then 22 | ret void 23 | } 24 | 25 | declare void @__quantum__rt__initialize(i8*) 26 | 27 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 28 | 29 | declare i1 @__quantum__qis__read_result__body(%Result*) 30 | 31 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" } 32 | attributes #1 = { "irreversible" } 33 | 34 | !llvm.module.flags = !{!0, !1, !2, !3} 35 | 36 | !0 = !{i32 1, !"qir_major_version", i32 1} 37 | !1 = !{i32 7, !"qir_minor_version", i32 0} 38 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 39 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 40 | -------------------------------------------------------------------------------- /tests/resources/test_single_register_index_variations_falsy.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_single_register_index_variations_falsy' 2 | source_filename = "test_single_register_index_variations" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | define void @test_single_register_index_variations() #0 { 8 | entry: 9 | call void @__quantum__rt__initialize(i8* null) 10 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 11 | %0 = call i1 @__quantum__qis__read_result__body(%Result* null) 12 | br i1 %0, label %then, label %else 13 | 14 | then: ; preds = %entry 15 | br label %continue 16 | 17 | else: ; preds = %entry 18 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 19 | br label %continue 20 | 21 | continue: ; preds = %else, %then 22 | ret void 23 | } 24 | 25 | declare void @__quantum__rt__initialize(i8*) 26 | 27 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 28 | 29 | declare i1 @__quantum__qis__read_result__body(%Result*) 30 | 31 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" } 32 | attributes #1 = { "irreversible" } 33 | 34 | !llvm.module.flags = !{!0, !1, !2, !3} 35 | 36 | !0 = !{i32 1, !"qir_major_version", i32 1} 37 | !1 = !{i32 7, !"qir_minor_version", i32 0} 38 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 39 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 40 | -------------------------------------------------------------------------------- /tests/resources/test_single_register_index_variations_truthy.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_single_register_index_variations_truthy' 2 | source_filename = "test_single_register_index_variations" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | define void @test_single_register_index_variations() #0 { 8 | entry: 9 | call void @__quantum__rt__initialize(i8* null) 10 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 11 | %0 = call i1 @__quantum__qis__read_result__body(%Result* null) 12 | br i1 %0, label %then, label %else 13 | 14 | then: ; preds = %entry 15 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 16 | br label %continue 17 | 18 | else: ; preds = %entry 19 | br label %continue 20 | 21 | continue: ; preds = %else, %then 22 | ret void 23 | } 24 | 25 | declare void @__quantum__rt__initialize(i8*) 26 | 27 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 28 | 29 | declare i1 @__quantum__qis__read_result__body(%Result*) 30 | 31 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" } 32 | attributes #1 = { "irreversible" } 33 | 34 | !llvm.module.flags = !{!0, !1, !2, !3} 35 | 36 | !0 = !{i32 1, !"qir_major_version", i32 1} 37 | !1 = !{i32 7, !"qir_minor_version", i32 0} 38 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 39 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 40 | -------------------------------------------------------------------------------- /tests/resources/test_single_register_variations_falsy.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_single_register_variations_falsy' 2 | source_filename = "test_single_register_variations" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | define void @test_single_register_variations() #0 { 8 | entry: 9 | call void @__quantum__rt__initialize(i8* null) 10 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 11 | %0 = call i1 @__quantum__qis__read_result__body(%Result* null) 12 | br i1 %0, label %then, label %else 13 | 14 | then: ; preds = %entry 15 | br label %continue 16 | 17 | else: ; preds = %entry 18 | %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) 19 | br i1 %1, label %then1, label %else2 20 | 21 | continue: ; preds = %continue3, %then 22 | ret void 23 | 24 | then1: ; preds = %else 25 | br label %continue3 26 | 27 | else2: ; preds = %else 28 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 29 | br label %continue3 30 | 31 | continue3: ; preds = %else2, %then1 32 | br label %continue 33 | } 34 | 35 | declare void @__quantum__rt__initialize(i8*) 36 | 37 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 38 | 39 | declare i1 @__quantum__qis__read_result__body(%Result*) 40 | 41 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" } 42 | attributes #1 = { "irreversible" } 43 | 44 | !llvm.module.flags = !{!0, !1, !2, !3} 45 | 46 | !0 = !{i32 1, !"qir_major_version", i32 1} 47 | !1 = !{i32 7, !"qir_minor_version", i32 0} 48 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 49 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 50 | -------------------------------------------------------------------------------- /tests/resources/test_single_register_variations_truthy.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_single_register_variations_truthy' 2 | source_filename = "test_single_register_variations" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | define void @test_single_register_variations() #0 { 8 | entry: 9 | call void @__quantum__rt__initialize(i8* null) 10 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 11 | %0 = call i1 @__quantum__qis__read_result__body(%Result* null) 12 | br i1 %0, label %then, label %else 13 | 14 | then: ; preds = %entry 15 | %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) 16 | br i1 %1, label %then1, label %else2 17 | 18 | else: ; preds = %entry 19 | br label %continue 20 | 21 | continue: ; preds = %continue3, %else 22 | ret void 23 | 24 | then1: ; preds = %then 25 | br label %continue3 26 | 27 | else2: ; preds = %then 28 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 29 | br label %continue3 30 | 31 | continue3: ; preds = %else2, %then1 32 | br label %continue 33 | } 34 | 35 | declare void @__quantum__rt__initialize(i8*) 36 | 37 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 38 | 39 | declare i1 @__quantum__qis__read_result__body(%Result*) 40 | 41 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" } 42 | attributes #1 = { "irreversible" } 43 | 44 | !llvm.module.flags = !{!0, !1, !2, !3} 45 | 46 | !0 = !{i32 1, !"qir_major_version", i32 1} 47 | !1 = !{i32 7, !"qir_minor_version", i32 0} 48 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 49 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 50 | -------------------------------------------------------------------------------- /tests/resources/test_two_bit_register_variations_falsy.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_two_bit_register_variations_falsy' 2 | source_filename = "test_two_bit_register_variations" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | define void @test_two_bit_register_variations() #0 { 8 | entry: 9 | call void @__quantum__rt__initialize(i8* null) 10 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 11 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 12 | %0 = call i1 @__quantum__qis__read_result__body(%Result* null) 13 | br i1 %0, label %then, label %else 14 | 15 | then: ; preds = %entry 16 | br label %continue 17 | 18 | else: ; preds = %entry 19 | %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) 20 | br i1 %1, label %then1, label %else2 21 | 22 | continue: ; preds = %continue3, %then 23 | ret void 24 | 25 | then1: ; preds = %else 26 | br label %continue3 27 | 28 | else2: ; preds = %else 29 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) 30 | br label %continue3 31 | 32 | continue3: ; preds = %else2, %then1 33 | br label %continue 34 | } 35 | 36 | declare void @__quantum__rt__initialize(i8*) 37 | 38 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 39 | 40 | declare i1 @__quantum__qis__read_result__body(%Result*) 41 | 42 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="3" "required_num_results"="3" } 43 | attributes #1 = { "irreversible" } 44 | 45 | !llvm.module.flags = !{!0, !1, !2, !3} 46 | 47 | !0 = !{i32 1, !"qir_major_version", i32 1} 48 | !1 = !{i32 7, !"qir_minor_version", i32 0} 49 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 50 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 51 | -------------------------------------------------------------------------------- /tests/resources/test_two_bit_register_variations_three.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_two_bit_register_variations_three' 2 | source_filename = "test_two_bit_register_variations" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | define void @test_two_bit_register_variations() #0 { 8 | entry: 9 | call void @__quantum__rt__initialize(i8* null) 10 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 11 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 12 | %0 = call i1 @__quantum__qis__read_result__body(%Result* null) 13 | br i1 %0, label %then, label %else 14 | 15 | then: ; preds = %entry 16 | %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) 17 | br i1 %1, label %then1, label %else2 18 | 19 | else: ; preds = %entry 20 | br label %continue 21 | 22 | continue: ; preds = %continue3, %else 23 | ret void 24 | 25 | then1: ; preds = %then 26 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) 27 | br label %continue3 28 | 29 | else2: ; preds = %then 30 | br label %continue3 31 | 32 | continue3: ; preds = %else2, %then1 33 | br label %continue 34 | } 35 | 36 | declare void @__quantum__rt__initialize(i8*) 37 | 38 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 39 | 40 | declare i1 @__quantum__qis__read_result__body(%Result*) 41 | 42 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="3" "required_num_results"="3" } 43 | attributes #1 = { "irreversible" } 44 | 45 | !llvm.module.flags = !{!0, !1, !2, !3} 46 | 47 | !0 = !{i32 1, !"qir_major_version", i32 1} 48 | !1 = !{i32 7, !"qir_minor_version", i32 0} 49 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 50 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 51 | -------------------------------------------------------------------------------- /tests/resources/test_two_bit_register_variations_truthy.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_two_bit_register_variations_truthy' 2 | source_filename = "test_two_bit_register_variations" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | define void @test_two_bit_register_variations() #0 { 8 | entry: 9 | call void @__quantum__rt__initialize(i8* null) 10 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 11 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 12 | %0 = call i1 @__quantum__qis__read_result__body(%Result* null) 13 | br i1 %0, label %then, label %else 14 | 15 | then: ; preds = %entry 16 | %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) 17 | br i1 %1, label %then1, label %else2 18 | 19 | else: ; preds = %entry 20 | br label %continue 21 | 22 | continue: ; preds = %continue3, %else 23 | ret void 24 | 25 | then1: ; preds = %then 26 | br label %continue3 27 | 28 | else2: ; preds = %then 29 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) 30 | br label %continue3 31 | 32 | continue3: ; preds = %else2, %then1 33 | br label %continue 34 | } 35 | 36 | declare void @__quantum__rt__initialize(i8*) 37 | 38 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 39 | 40 | declare i1 @__quantum__qis__read_result__body(%Result*) 41 | 42 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="3" "required_num_results"="3" } 43 | attributes #1 = { "irreversible" } 44 | 45 | !llvm.module.flags = !{!0, !1, !2, !3} 46 | 47 | !0 = !{i32 1, !"qir_major_version", i32 1} 48 | !1 = !{i32 7, !"qir_minor_version", i32 0} 49 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 50 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 51 | -------------------------------------------------------------------------------- /tests/resources/test_two_bit_register_variations_two.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_two_bit_register_variations_two' 2 | source_filename = "test_two_bit_register_variations" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | define void @test_two_bit_register_variations() #0 { 8 | entry: 9 | call void @__quantum__rt__initialize(i8* null) 10 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 11 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 12 | %0 = call i1 @__quantum__qis__read_result__body(%Result* null) 13 | br i1 %0, label %then, label %else 14 | 15 | then: ; preds = %entry 16 | br label %continue 17 | 18 | else: ; preds = %entry 19 | %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) 20 | br i1 %1, label %then1, label %else2 21 | 22 | continue: ; preds = %continue3, %then 23 | ret void 24 | 25 | then1: ; preds = %else 26 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) 27 | br label %continue3 28 | 29 | else2: ; preds = %else 30 | br label %continue3 31 | 32 | continue3: ; preds = %else2, %then1 33 | br label %continue 34 | } 35 | 36 | declare void @__quantum__rt__initialize(i8*) 37 | 38 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 39 | 40 | declare i1 @__quantum__qis__read_result__body(%Result*) 41 | 42 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="3" "required_num_results"="3" } 43 | attributes #1 = { "irreversible" } 44 | 45 | !llvm.module.flags = !{!0, !1, !2, !3} 46 | 47 | !0 = !{i32 1, !"qir_major_version", i32 1} 48 | !1 = !{i32 7, !"qir_minor_version", i32 0} 49 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 50 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 51 | -------------------------------------------------------------------------------- /tests/test_batching.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | from qiskit_qir.translate import to_qir_module 6 | from qiskit import QuantumCircuit, ClassicalRegister 7 | from qiskit.circuit import Parameter 8 | import numpy as np 9 | from pyqir import Context, Module, is_entry_point 10 | from typing import List 11 | import test_utils 12 | import pytest 13 | 14 | 15 | def get_parameterized_circuit(num_qubits: int, num_params: int) -> List[QuantumCircuit]: 16 | theta_range = np.linspace(0, 2 * np.pi, num_params) 17 | 18 | theta = Parameter("θ") 19 | circuit = QuantumCircuit(num_qubits, 1) 20 | 21 | circuit.h(0) 22 | for i in range(num_qubits - 1): 23 | circuit.cx(i, i + 1) 24 | 25 | circuit.barrier() 26 | circuit.rz(theta, range(num_qubits)) 27 | circuit.barrier() 28 | 29 | for i in reversed(range(num_qubits - 1)): 30 | circuit.cx(i, i + 1) 31 | circuit.h(0) 32 | circuit.measure(0, 0) 33 | 34 | circuits = [ 35 | circuit.assign_parameters({theta: theta_val}, inplace=False) 36 | for theta_val in theta_range 37 | ] 38 | 39 | return circuits 40 | 41 | 42 | @pytest.mark.parametrize("num_params", [1, 2, 3]) 43 | def test_binding_generates_corresponding_entry_points(num_params: int) -> None: 44 | circuits = get_parameterized_circuit(2, num_params) 45 | bitcode = to_qir_module(circuits)[0].bitcode 46 | mod = Module.from_bitcode(Context(), bitcode) 47 | funcs = list(filter(is_entry_point, mod.functions)) 48 | assert len(funcs) == num_params 49 | 50 | 51 | def test_batch_entry_points_use_circuit_names() -> None: 52 | qc1 = QuantumCircuit(1, name="first") 53 | qc2 = QuantumCircuit(1, name="second") 54 | module, entry_points = to_qir_module(list([qc1, qc2])) 55 | mod = Module.from_bitcode(Context(), module.bitcode) 56 | functions = list(filter(is_entry_point, mod.functions)) 57 | assert len(functions) == 2 58 | for function in functions: 59 | assert function.name in ["first", "second"] 60 | assert entry_points == list([x.name for x in functions]) 61 | 62 | 63 | def test_batch_entry_points_make_unique_names_on_duplicates() -> None: 64 | name = "first" 65 | qc1 = QuantumCircuit(1, name=name) 66 | qc2 = QuantumCircuit(1, name=name) 67 | module, entry_points = to_qir_module(list([qc1, qc2])) 68 | mod = Module.from_bitcode(Context(), module.bitcode) 69 | functions = list(filter(is_entry_point, mod.functions)) 70 | assert len(functions) == 2 71 | for function in functions: 72 | assert function.name.startswith(name) 73 | assert functions[0].name != functions[1].name 74 | assert entry_points == list([x.name for x in functions]) 75 | 76 | 77 | def test_batch_entry_points_have_appropriate_attributes() -> None: 78 | qc1 = QuantumCircuit(1, 2, name="first") 79 | qc2 = QuantumCircuit(1, name="second") 80 | qc3 = QuantumCircuit(name="second") 81 | qc4 = QuantumCircuit(name="second") 82 | cr = ClassicalRegister(2, "creg") 83 | qc4.add_register(cr) 84 | bitcode = to_qir_module(list([qc1, qc2, qc3, qc4]))[0].bitcode 85 | mod = Module.from_bitcode(Context(), bitcode) 86 | functions = list(filter(is_entry_point, mod.functions)) 87 | assert len(functions) == 4 88 | test_utils.check_attributes_on_entrypoint(functions[0], 1, 2) 89 | test_utils.check_attributes_on_entrypoint(functions[1], 1) 90 | test_utils.check_attributes_on_entrypoint(functions[2]) 91 | test_utils.check_attributes_on_entrypoint(functions[3], expected_results=2) 92 | 93 | 94 | def test_passing_list_with_non_quantum_circuits_raises_value_error() -> None: 95 | qc = QuantumCircuit(1) 96 | with pytest.raises(ValueError): 97 | _ = to_qir_module(list([qc, 2])) 98 | with pytest.raises(ValueError): 99 | _ = to_qir_module(list([2, qc])) 100 | with pytest.raises(ValueError): 101 | _ = to_qir_module(list([2])) 102 | with pytest.raises(ValueError): 103 | _ = to_qir_module(list([qc, 2, qc])) 104 | 105 | 106 | def test_passing_non_quantum_circuits_raises_value_error() -> None: 107 | with pytest.raises(ValueError): 108 | _ = to_qir_module(2) 109 | 110 | 111 | def test_passing_empty_list_of_quantum_circuits_raises_value_error() -> None: 112 | with pytest.raises(ValueError): 113 | _ = to_qir_module(list([])) 114 | -------------------------------------------------------------------------------- /tests/test_c_if.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | 6 | from qiskit_qir.translate import to_qir_module 7 | from qiskit import ClassicalRegister, QuantumCircuit 8 | from qiskit.circuit import Clbit 9 | from qiskit.circuit.exceptions import CircuitError 10 | 11 | import pytest 12 | import pyqir 13 | 14 | import os 15 | from pathlib import Path 16 | 17 | # result_stream, condition value, expected gates 18 | falsy_single_bit_variations = [False, 0] 19 | 20 | truthy_single_bit_variations = [True, 1] 21 | 22 | invalid_single_bit_varitions = [-1] 23 | 24 | 25 | def compare_reference_ir(generated_bitcode: bytes, name: str) -> None: 26 | module = pyqir.Module.from_bitcode(pyqir.Context(), generated_bitcode, f"{name}") 27 | ir = str(module) 28 | 29 | file = os.path.join(os.path.dirname(__file__), f"resources/{name}.ll") 30 | expected = Path(file).read_text() 31 | assert ir == expected 32 | 33 | 34 | @pytest.mark.parametrize("value", falsy_single_bit_variations) 35 | def test_single_clbit_variations_falsy(value: bool) -> None: 36 | circuit = QuantumCircuit(2, 0, name=f"test_single_clbit_variations") 37 | cr = ClassicalRegister(2, "creg") 38 | circuit.add_register(cr) 39 | circuit.measure(0, 0) 40 | bit: Clbit = cr[0] 41 | circuit.measure(1, 1).c_if(bit, value) 42 | 43 | generated_bitcode = to_qir_module(circuit, record_output=False)[0].bitcode 44 | compare_reference_ir(generated_bitcode, "test_single_clbit_variations_falsy") 45 | 46 | 47 | @pytest.mark.parametrize("value", truthy_single_bit_variations) 48 | def test_single_clbit_variations_truthy(value: bool) -> None: 49 | circuit = QuantumCircuit(2, 0, name=f"test_single_clbit_variations") 50 | cr = ClassicalRegister(2, "creg") 51 | circuit.add_register(cr) 52 | circuit.measure(0, 0) 53 | bit: Clbit = cr[0] 54 | circuit.measure(1, 1).c_if(bit, value) 55 | 56 | generated_bitcode = to_qir_module(circuit, record_output=False)[0].bitcode 57 | compare_reference_ir(generated_bitcode, "test_single_clbit_variations_truthy") 58 | 59 | 60 | @pytest.mark.parametrize("value", truthy_single_bit_variations) 61 | def test_single_register_index_variations_truthy(value: bool) -> None: 62 | circuit = QuantumCircuit(2, 0, name=f"test_single_register_index_variations") 63 | cr = ClassicalRegister(2, "creg") 64 | circuit.add_register(cr) 65 | circuit.measure(0, 0) 66 | circuit.measure(1, 1).c_if(0, value) 67 | 68 | generated_bitcode = to_qir_module(circuit, record_output=False)[0].bitcode 69 | 70 | compare_reference_ir( 71 | generated_bitcode, "test_single_register_index_variations_truthy" 72 | ) 73 | 74 | 75 | @pytest.mark.parametrize("value", falsy_single_bit_variations) 76 | def test_single_register_index_variations_falsy(value: bool) -> None: 77 | circuit = QuantumCircuit(2, 0, name=f"test_single_register_index_variations") 78 | cr = ClassicalRegister(2, "creg") 79 | circuit.add_register(cr) 80 | circuit.measure(0, 0) 81 | circuit.measure(1, 1).c_if(0, value) 82 | 83 | generated_bitcode = to_qir_module(circuit, record_output=False)[0].bitcode 84 | 85 | compare_reference_ir( 86 | generated_bitcode, "test_single_register_index_variations_falsy" 87 | ) 88 | 89 | 90 | @pytest.mark.parametrize("value", truthy_single_bit_variations) 91 | def test_single_register_variations_truthy(value: bool) -> None: 92 | circuit = QuantumCircuit(2, 0, name=f"test_single_register_variations") 93 | cr = ClassicalRegister(2, "creg") 94 | circuit.add_register(cr) 95 | circuit.measure(0, 0) 96 | circuit.measure(1, 1).c_if(cr, value) 97 | 98 | generated_bitcode = to_qir_module(circuit, record_output=False)[0].bitcode 99 | 100 | compare_reference_ir(generated_bitcode, "test_single_register_variations_truthy") 101 | 102 | 103 | @pytest.mark.parametrize("value", falsy_single_bit_variations) 104 | def test_single_register_variations_falsy(value: bool) -> None: 105 | circuit = QuantumCircuit(2, 0, name=f"test_single_register_variations") 106 | cr = ClassicalRegister(2, "creg") 107 | circuit.add_register(cr) 108 | circuit.measure(0, 0) 109 | circuit.measure(1, 1).c_if(cr, value) 110 | 111 | generated_bitcode = to_qir_module(circuit, record_output=False)[0].bitcode 112 | 113 | compare_reference_ir(generated_bitcode, "test_single_register_variations_falsy") 114 | 115 | 116 | @pytest.mark.parametrize("value", invalid_single_bit_varitions) 117 | def test_single_clbit_invalid_variations(value: int) -> None: 118 | circuit = QuantumCircuit(2, 0, name=f"test_single_clbit_invalid_variations") 119 | cr = ClassicalRegister(2, "creg") 120 | circuit.add_register(cr) 121 | circuit.measure(0, 0) 122 | bit: Clbit = cr[0] 123 | 124 | with pytest.raises(CircuitError) as exc_info: 125 | _ = circuit.measure(1, 1).c_if(bit, value) 126 | 127 | assert exc_info is not None 128 | 129 | 130 | @pytest.mark.parametrize("value", invalid_single_bit_varitions) 131 | def test_single_register_index_invalid_variations(value: int) -> None: 132 | circuit = QuantumCircuit( 133 | 2, 134 | 0, 135 | name=f"test_single_register_index_invalid_variations", 136 | ) 137 | cr = ClassicalRegister(2, "creg") 138 | circuit.add_register(cr) 139 | circuit.measure(0, 0) 140 | 141 | with pytest.raises(CircuitError) as exc_info: 142 | _ = circuit.measure(1, 1).c_if(0, value) 143 | 144 | assert exc_info is not None 145 | 146 | 147 | @pytest.mark.parametrize("value", invalid_single_bit_varitions) 148 | def test_single_register_invalid_variations(value: int) -> None: 149 | circuit = QuantumCircuit(2, 0, name=f"test_single_register_invalid_variations") 150 | cr = ClassicalRegister(2, "creg") 151 | circuit.add_register(cr) 152 | circuit.measure(0, 0) 153 | 154 | with pytest.raises(CircuitError) as exc_info: 155 | _ = circuit.measure(1, 1).c_if(cr, value) 156 | 157 | assert exc_info is not None 158 | 159 | 160 | two_bit_variations = [ 161 | [False, "falsy"], 162 | [0, "falsy"], 163 | [True, "truthy"], 164 | [1, "truthy"], 165 | [2, "two"], 166 | [3, "three"], 167 | ] 168 | 169 | # # -1: 11 170 | # # -2: 10 171 | # # -3: 01 172 | # # -4: 00 173 | invalid_two_bit_variations = [-4, -3, -2, -1] 174 | 175 | 176 | @pytest.mark.parametrize("matrix", two_bit_variations) 177 | def test_two_bit_register_variations(matrix) -> None: 178 | value, name = matrix 179 | circuit = QuantumCircuit( 180 | 3, 181 | 0, 182 | name=f"test_two_bit_register_variations", 183 | ) 184 | 185 | cr = ClassicalRegister(2, "creg") 186 | circuit.add_register(cr) 187 | cond = ClassicalRegister(1, "cond") 188 | circuit.add_register(cond) 189 | 190 | circuit.measure(0, 0) 191 | circuit.measure(1, 1) 192 | circuit.measure(2, 2).c_if(cr, value) 193 | 194 | generated_bitcode = to_qir_module(circuit, record_output=False)[0].bitcode 195 | 196 | compare_reference_ir(generated_bitcode, f"test_two_bit_register_variations_{name}") 197 | 198 | 199 | @pytest.mark.parametrize("value", invalid_two_bit_variations) 200 | def test_two_bit_register_invalid_variations(value: int) -> None: 201 | circuit = QuantumCircuit( 202 | 3, 203 | 0, 204 | name=f"test_two_bit_register_invalid_variations", 205 | ) 206 | 207 | cr = ClassicalRegister(2, "creg") 208 | circuit.add_register(cr) 209 | cond = ClassicalRegister(1, "cond") 210 | circuit.add_register(cond) 211 | 212 | circuit.measure(0, 0) 213 | circuit.measure(1, 1) 214 | 215 | with pytest.raises(CircuitError) as exc_info: 216 | _ = circuit.measure(2, 2).c_if(cr, value) 217 | 218 | assert exc_info is not None 219 | -------------------------------------------------------------------------------- /tests/test_capabilities.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | from typing import List 6 | 7 | import pytest 8 | from qiskit_qir.elements import QiskitModule 9 | 10 | from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit 11 | from qiskit_qir.capability import ( 12 | Capability, 13 | ConditionalBranchingOnResultError, 14 | QubitUseAfterMeasurementError, 15 | ) 16 | from qiskit_qir.visitor import BasicQisVisitor 17 | 18 | # test circuits 19 | 20 | 21 | def teleport() -> QuantumCircuit: 22 | qq = QuantumRegister(3, name="qq") 23 | cr = ClassicalRegister(2, name="cr") 24 | circuit = QuantumCircuit(qq, cr, name="Teleport") 25 | circuit.h(1) 26 | circuit.cx(1, 2) 27 | circuit.cx(0, 1) 28 | circuit.h(0) 29 | circuit.measure(0, 0) 30 | circuit.measure(1, 1) 31 | circuit.x(2).c_if(cr, int("10", 2)) 32 | circuit.z(2).c_if(cr, int("01", 2)) 33 | return circuit 34 | 35 | 36 | def use_after_measure(): 37 | qq = QuantumRegister(2, name="qq") 38 | cr = ClassicalRegister(2, name="cr") 39 | circuit = QuantumCircuit(qq, cr) 40 | 41 | circuit.h(1) 42 | circuit.measure(1, 1) 43 | circuit.h(1) 44 | 45 | return circuit 46 | 47 | 48 | def use_another_after_measure(): 49 | circuit = QuantumCircuit(3, 2) 50 | 51 | circuit.h(0) 52 | circuit.measure(0, 0) 53 | circuit.h(1) 54 | circuit.cx(1, 2) 55 | circuit.measure(1, 1) 56 | 57 | return circuit 58 | 59 | 60 | def use_another_after_measure_and_condition(): 61 | qq = QuantumRegister(3, name="qq") 62 | cr = ClassicalRegister(2, name="cr") 63 | circuit = QuantumCircuit(qq, cr) 64 | 65 | circuit.h(0) 66 | circuit.measure(0, 0) 67 | circuit.h(1) 68 | circuit.cx(1, 2) 69 | circuit.measure(1, 1) 70 | circuit.x(2).c_if(cr, int("10", 2)) 71 | 72 | return circuit 73 | 74 | 75 | def use_conditional_branch_on_single_register_true_value(): 76 | circuit = QuantumCircuit(name="Conditional") 77 | qr = QuantumRegister(2, "qreg") 78 | cr = ClassicalRegister(3, "creg") 79 | circuit.add_register(qr) 80 | circuit.add_register(cr) 81 | circuit.x(0) 82 | circuit.measure(0, 0) 83 | circuit.x(1).c_if(cr[2], 1) 84 | circuit.measure(0, 1) 85 | 86 | return circuit 87 | 88 | 89 | def use_conditional_branch_on_single_register_false_value(): 90 | circuit = QuantumCircuit(name="Conditional") 91 | qr = QuantumRegister(2, "qreg") 92 | cr = ClassicalRegister(3, "creg") 93 | circuit.add_register(qr) 94 | circuit.add_register(cr) 95 | circuit.x(0) 96 | circuit.measure(0, 0) 97 | circuit.x(1).c_if(cr[2], 0) 98 | circuit.measure(0, 1) 99 | 100 | return circuit 101 | 102 | 103 | def conditional_branch_on_bit(): 104 | qr = QuantumRegister(2, "qreg") 105 | cr = ClassicalRegister(2, "creg") 106 | circuit = QuantumCircuit(qr, cr, name="conditional_branch_on_bit") 107 | circuit.x(0) 108 | circuit.measure(0, 0) 109 | circuit.x(1).c_if(cr[0], 1) 110 | circuit.measure(1, 1) 111 | return circuit 112 | 113 | 114 | # Utility using new visitor 115 | def circuit_to_qir(circuit, profile: str = "AdaptiveExecution"): 116 | module = QiskitModule.from_quantum_circuit(circuit=circuit) 117 | visitor = BasicQisVisitor(profile) 118 | module.accept(visitor) 119 | return visitor.ir() 120 | 121 | 122 | def test_branching_on_measurement_fails_without_required_capability(): 123 | circuit = teleport() 124 | with pytest.raises(ConditionalBranchingOnResultError) as exc_info: 125 | _ = circuit_to_qir(circuit, "BasicExecution") 126 | 127 | exception_raised = exc_info.value 128 | assert ( 129 | str(exception_raised.instruction) 130 | == "Instruction(name='x', num_qubits=1, num_clbits=0, params=[])" 131 | ) 132 | assert ( 133 | str(exception_raised.instruction.condition) == "(ClassicalRegister(2, 'cr'), 2)" 134 | ) 135 | assert str(exception_raised.qargs) == "[Qubit(QuantumRegister(3, 'qq'), 2)]" 136 | assert str(exception_raised.cargs) == "[]" 137 | assert str(exception_raised.profile) == "BasicExecution" 138 | assert exception_raised.instruction_string == "if(cr == 2) x qq[2]" 139 | 140 | 141 | def test_branching_on_measurement_fails_without_required_capability(): 142 | circuit = use_conditional_branch_on_single_register_true_value() 143 | with pytest.raises(ConditionalBranchingOnResultError) as exc_info: 144 | _ = circuit_to_qir(circuit, "BasicExecution") 145 | 146 | exception_raised = exc_info.value 147 | assert ( 148 | str(exception_raised.instruction) 149 | == "Instruction(name='x', num_qubits=1, num_clbits=0, params=[])" 150 | ) 151 | assert ( 152 | str(exception_raised.instruction.condition) 153 | == "(Clbit(ClassicalRegister(3, 'creg'), 2), True)" 154 | ) 155 | assert str(exception_raised.qargs) == "[Qubit(QuantumRegister(2, 'qreg'), 1)]" 156 | assert str(exception_raised.cargs) == "[]" 157 | assert str(exception_raised.profile) == "BasicExecution" 158 | assert exception_raised.instruction_string == "if(creg[2] == True) x qreg[1]" 159 | 160 | 161 | def test_branching_on_measurement_fails_without_required_capability(): 162 | circuit = use_conditional_branch_on_single_register_false_value() 163 | with pytest.raises(ConditionalBranchingOnResultError) as exc_info: 164 | _ = circuit_to_qir(circuit, "BasicExecution") 165 | 166 | exception_raised = exc_info.value 167 | assert ( 168 | str(exception_raised.instruction) 169 | == "Instruction(name='x', num_qubits=1, num_clbits=0, params=[])" 170 | ) 171 | assert ( 172 | str(exception_raised.instruction.condition) 173 | == "(Clbit(ClassicalRegister(3, 'creg'), 2), False)" 174 | ) 175 | assert str(exception_raised.qargs) == "[Qubit(QuantumRegister(2, 'qreg'), 1)]" 176 | assert str(exception_raised.cargs) == "[]" 177 | assert str(exception_raised.profile) == "BasicExecution" 178 | assert exception_raised.instruction_string == "if(creg[2] == False) x qreg[1]" 179 | 180 | 181 | def test_branching_on_measurement_register_passses_with_required_capability(): 182 | circuit = teleport() 183 | _ = circuit_to_qir(circuit) 184 | 185 | 186 | def test_branching_on_measurement_bit_passses_with_required_capability(): 187 | circuit = conditional_branch_on_bit() 188 | _ = circuit_to_qir(circuit) 189 | 190 | 191 | def test_reuse_after_measurement_fails_without_required_capability(): 192 | circuit = use_after_measure() 193 | with pytest.raises(QubitUseAfterMeasurementError) as exc_info: 194 | _ = circuit_to_qir(circuit, "BasicExecution") 195 | 196 | exception_raised = exc_info.value 197 | assert ( 198 | str(exception_raised.instruction) 199 | == "Instruction(name='h', num_qubits=1, num_clbits=0, params=[])" 200 | ) 201 | assert exception_raised.instruction.condition is None 202 | assert str(exception_raised.qargs) == "[Qubit(QuantumRegister(2, 'qq'), 1)]" 203 | assert str(exception_raised.cargs) == "[]" 204 | assert str(exception_raised.profile) == "BasicExecution" 205 | assert exception_raised.instruction_string == "h qq[1]" 206 | 207 | 208 | def test_reuse_after_measurement_passes_with_required_capability(): 209 | circuit = use_after_measure() 210 | _ = circuit_to_qir(circuit) 211 | 212 | 213 | def test_using_an_unread_qubit_after_measuring_passes_without_required_capability(): 214 | circuit = use_another_after_measure() 215 | _ = circuit_to_qir(circuit, "BasicExecution") 216 | 217 | 218 | def test_use_another_after_measure_and_condition_passes_with_required_capability(): 219 | circuit = use_another_after_measure_and_condition() 220 | _ = circuit_to_qir(circuit) 221 | -------------------------------------------------------------------------------- /tests/test_circuits/__init__.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | 6 | from .random import * 7 | from .basic_circuits import * 8 | 9 | # Core test fixtures 10 | core_tests = [ 11 | "ghz", 12 | "teleport", 13 | "unroll", 14 | "teleport_with_subroutine", 15 | "measure_x_as_subroutine", 16 | ] + random_fixtures 17 | 18 | noop_tests = ["bernstein_vazirani_with_delay", "ghz_with_delay"] 19 | -------------------------------------------------------------------------------- /tests/test_circuits/basic_circuits.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | from builtins import format 6 | import pytest 7 | 8 | from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister 9 | 10 | 11 | @pytest.fixture() 12 | def ghz(): 13 | circuit = QuantumCircuit(4, 3) 14 | circuit.name = "Qiskit Sample - 3-qubit GHZ circuit" 15 | circuit.h(0) 16 | circuit.cx(0, 1) 17 | circuit.cx(1, 2) 18 | circuit.measure([0, 1, 2], [0, 1, 2]) 19 | 20 | return circuit 21 | 22 | 23 | @pytest.fixture() 24 | def teleport(): 25 | q = QuantumRegister(3, name="q") 26 | cr = ClassicalRegister(2, name="cr") 27 | circuit = QuantumCircuit(q, cr, name="Teleport") 28 | circuit.h(1) 29 | circuit.cx(1, 2) 30 | circuit.cx(0, 1) 31 | circuit.h(0) 32 | circuit.measure(0, 0) 33 | circuit.measure(1, 1) 34 | circuit.x(2).c_if(cr, int("10", 2)) 35 | circuit.z(2).c_if(cr, int("01", 2)) 36 | 37 | return circuit 38 | 39 | 40 | @pytest.fixture() 41 | def unroll(): 42 | circ = QuantumCircuit(3) 43 | circ.ccx(0, 1, 2) 44 | circ.crz(theta=0.1, control_qubit=0, target_qubit=1) 45 | circ.id(2) 46 | 47 | return circ.decompose() 48 | 49 | 50 | @pytest.fixture() 51 | def teleport_with_subroutine(): 52 | bell_circ = QuantumCircuit(2, name="CreateBellPair") 53 | bell_circ.h(0) 54 | bell_circ.cx(0, 1) 55 | q = QuantumRegister(3, name="q") 56 | cr = ClassicalRegister(2, name="cr") 57 | circuit = QuantumCircuit(q, cr, name="Teleport") 58 | circuit.append(bell_circ.to_instruction(), [1, 2]) 59 | circuit.cx(0, 1) 60 | circuit.h(0) 61 | circuit.measure(0, 0) 62 | circuit.measure(1, 1) 63 | circuit.x(2).c_if(cr, int("10", 2)) 64 | circuit.z(2).c_if(cr, int("01", 2)) 65 | 66 | return circuit 67 | 68 | 69 | @pytest.fixture() 70 | def bernstein_vazirani_with_delay(): 71 | num_qubits = 5 72 | qq = QuantumRegister(num_qubits + 1, name="qq") 73 | cr = ClassicalRegister(num_qubits, name="cr") 74 | 75 | circuit = QuantumCircuit(qq, cr, name="Bernstein-Vazirani") 76 | 77 | circuit.h(num_qubits) 78 | circuit.z(num_qubits) 79 | 80 | for index in range(num_qubits): 81 | circuit.h(index) 82 | 83 | circuit.delay(42, qq[0], "ps") 84 | 85 | oracle = format(2, "b").zfill(num_qubits) 86 | oracle = oracle[::-1] 87 | for index in range(num_qubits): 88 | if oracle[index] == "0": 89 | circuit.id(index) 90 | else: 91 | circuit.cx(index, num_qubits) 92 | 93 | circuit.delay(23, 2, "ms") 94 | 95 | for index in range(num_qubits): 96 | circuit.h(index) 97 | 98 | for index in range(num_qubits): 99 | circuit.measure(index, index) 100 | 101 | return circuit 102 | 103 | 104 | @pytest.fixture() 105 | def ghz_with_delay(): 106 | qq = QuantumRegister(4, name="qq") 107 | cr = ClassicalRegister(3, name="cr") 108 | circuit = QuantumCircuit(qq, cr) 109 | circuit.name = "Qiskit Sample - 3-qubit GHZ circuit" 110 | circuit.delay(23.54, None, "us") 111 | circuit.h(0) 112 | circuit.delay(42, qq[0], "ps") 113 | circuit.cx(0, 1) 114 | circuit.delay(23, 2, "ms") 115 | circuit.cx(1, 2) 116 | circuit.delay(3, qq[1]) 117 | circuit.measure([0, 1, 2], [0, 1, 2]) 118 | 119 | return circuit 120 | 121 | 122 | @pytest.fixture() 123 | def measure_x_as_subroutine(): 124 | measure_x_circuit = QuantumCircuit(1, 1, name="measure_x") 125 | measure_x_circuit.h(0) 126 | measure_x_circuit.measure(0, 0) 127 | measure_x_circuit.h(0) 128 | measure_x_gate = measure_x_circuit.to_instruction() 129 | qq = QuantumRegister(1, name="qq") 130 | cr = ClassicalRegister(1, name="cr") 131 | circuit = QuantumCircuit(qq, cr) 132 | circuit.name = "Qiskit Sample - Measure in the X-basis as a subroutine" 133 | circuit.append(measure_x_gate, [0], [0]) 134 | 135 | return circuit 136 | -------------------------------------------------------------------------------- /tests/test_circuits/basic_gates.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | import pytest 6 | 7 | from qiskit import QuantumCircuit 8 | 9 | # All of the following dictionaries map from the names of methods on Qiskit QuantumCircuit objects 10 | # to the name of the equivalent pyqir BasicQisBuilder method 11 | 12 | _zero_qubit_operations = {"barrier": "barrier"} 13 | 14 | _one_qubit_gates = { 15 | "h": "h", 16 | "reset": "reset", 17 | "s": "s", 18 | "t": "t", 19 | "x": "x", 20 | "y": "y", 21 | "z": "z", 22 | } 23 | 24 | _adj_gates = {"sdg": "s", "tdg": "t"} 25 | 26 | _measurements = {"measure": "mz"} 27 | 28 | _rotations = {"rx": "rx", "ry": "ry", "rz": "rz"} 29 | 30 | _two_qubit_gates = {"cx": "cnot", "cz": "cz", "swap": "swap"} 31 | 32 | _three_qubit_gates = {"ccx": "ccx"} 33 | 34 | 35 | def _fixture_name(s: str) -> str: 36 | return f"Fixture_{s}" 37 | 38 | 39 | def _map_gate_name(gate: str) -> str: 40 | if gate in _one_qubit_gates: 41 | return _one_qubit_gates[gate] 42 | elif gate in _adj_gates: 43 | return _adj_gates[gate] 44 | elif gate in _measurements: 45 | return _measurements[gate] 46 | elif gate in _rotations: 47 | return _rotations[gate] 48 | elif gate in _two_qubit_gates: 49 | return _two_qubit_gates[gate] 50 | elif gate in _three_qubit_gates: 51 | return _three_qubit_gates[gate] 52 | elif gate in _zero_qubit_operations: 53 | return _zero_qubit_operations[gate] 54 | else: 55 | raise ValueError(f"Unknown Qiskit gate {gate}") 56 | 57 | 58 | def _generate_one_qubit_fixture(gate: str): 59 | @pytest.fixture() 60 | def test_fixture(): 61 | circuit = QuantumCircuit(1) 62 | getattr(circuit, gate)(0) 63 | return _map_gate_name(gate), circuit 64 | 65 | return test_fixture 66 | 67 | 68 | # Generate simple single-qubit gate fixtures 69 | for gate in _one_qubit_gates.keys(): 70 | name = _fixture_name(gate) 71 | locals()[name] = _generate_one_qubit_fixture(gate) 72 | 73 | # Generate adjoint single-qubit gate fixtures 74 | for gate in _adj_gates.keys(): 75 | name = _fixture_name(gate) 76 | locals()[name] = _generate_one_qubit_fixture(gate) 77 | 78 | 79 | def _generate_rotation_fixture(gate: str): 80 | @pytest.fixture() 81 | def test_fixture(): 82 | circuit = QuantumCircuit(1) 83 | getattr(circuit, gate)(0.5, 0) 84 | return _map_gate_name(gate), circuit 85 | 86 | return test_fixture 87 | 88 | 89 | # Generate rotation gate fixtures 90 | for gate in _rotations.keys(): 91 | name = _fixture_name(gate) 92 | locals()[name] = _generate_rotation_fixture(gate) 93 | 94 | 95 | def _generate_two_qubit_fixture(gate: str): 96 | @pytest.fixture() 97 | def test_fixture(): 98 | circuit = QuantumCircuit(2) 99 | getattr(circuit, gate)(0, 1) 100 | return _map_gate_name(gate), circuit 101 | 102 | return test_fixture 103 | 104 | 105 | # Generate double-qubit gate fixtures 106 | for gate in _two_qubit_gates.keys(): 107 | name = _fixture_name(gate) 108 | locals()[name] = _generate_two_qubit_fixture(gate) 109 | 110 | 111 | def _generate_three_qubit_fixture(gate: str): 112 | @pytest.fixture() 113 | def test_fixture(): 114 | circuit = QuantumCircuit(3) 115 | getattr(circuit, gate)(2, 0, 1) 116 | return _map_gate_name(gate), circuit 117 | 118 | return test_fixture 119 | 120 | 121 | # Generate three-qubit gate fixtures 122 | for gate in _three_qubit_gates.keys(): 123 | name = _fixture_name(gate) 124 | locals()[name] = _generate_three_qubit_fixture(gate) 125 | 126 | 127 | def _generate_measurement_fixture(gate: str): 128 | @pytest.fixture() 129 | def test_fixture(): 130 | circuit = QuantumCircuit(1, 1) 131 | getattr(circuit, gate)(0, 0) 132 | return _map_gate_name(gate), circuit 133 | 134 | return test_fixture 135 | 136 | 137 | # Generate measurement fixtures 138 | for gate in _measurements.keys(): 139 | name = _fixture_name(gate) 140 | locals()[name] = _generate_measurement_fixture(gate) 141 | 142 | single_op_tests = [_fixture_name(s) for s in _one_qubit_gates.keys()] 143 | adj_op_tests = [_fixture_name(s) for s in _adj_gates.keys()] 144 | rotation_tests = [_fixture_name(s) for s in _rotations.keys()] 145 | double_op_tests = [_fixture_name(s) for s in _two_qubit_gates.keys()] 146 | triple_op_tests = [_fixture_name(s) for s in _three_qubit_gates.keys()] 147 | measurement_tests = [_fixture_name(s) for s in _measurements.keys()] 148 | -------------------------------------------------------------------------------- /tests/test_circuits/control_flow_circuits.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | import pytest 6 | 7 | from qiskit import QuantumCircuit 8 | 9 | 10 | @pytest.fixture() 11 | def while_loop(): 12 | circuit = QuantumCircuit(1, 1) 13 | circuit.name = "Simple while-loop circuit" 14 | with circuit.while_loop((circuit.clbits[0], 0)): 15 | circuit.h(0) 16 | circuit.measure(0, 0) 17 | return circuit 18 | 19 | 20 | @pytest.fixture() 21 | def for_loop(): 22 | circuit = QuantumCircuit(4, 0) 23 | circuit.name = "Simple for-loop circuit" 24 | circuit.h(3) 25 | with circuit.for_loop(range(3)): 26 | # Qiskit doesn't (yet) support cnot(3, i) 27 | circuit.cx(3, 0) 28 | return circuit 29 | 30 | 31 | @pytest.fixture() 32 | def if_else(): 33 | circuit = QuantumCircuit(3, 2) 34 | 35 | circuit.h(0) 36 | circuit.cx(0, 1) 37 | circuit.measure(0, 0) 38 | circuit.h(0) 39 | circuit.cx(0, 1) 40 | circuit.measure(0, 1) 41 | 42 | with circuit.if_test((circuit.clbits[0], 0)) as else_: 43 | circuit.x(2) 44 | with else_: 45 | circuit.h(2) 46 | circuit.z(2) 47 | return circuit 48 | 49 | 50 | cf_fixtures = ["while_loop", "for_loop", "if_else"] 51 | -------------------------------------------------------------------------------- /tests/test_circuits/random.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | import pytest 6 | 7 | from qiskit import transpile 8 | from qiskit.circuit.random import random_circuit 9 | from qiskit_qir.visitor import SUPPORTED_INSTRUCTIONS 10 | 11 | 12 | def _generate_random_fixture(num_qubits, depth): 13 | @pytest.fixture() 14 | def random(): 15 | circuit = random_circuit(num_qubits, depth, measure=True) 16 | return transpile(circuit, basis_gates=SUPPORTED_INSTRUCTIONS) 17 | 18 | return random 19 | 20 | 21 | # Generate random fixtures 22 | random_fixtures = [] 23 | for num_qubits, depth in [(i + 2, j + 2) for i in range(9) for j in range(9)]: 24 | name = f"random_{num_qubits}x{depth}" 25 | locals()[name] = _generate_random_fixture(num_qubits, depth) 26 | random_fixtures.append(name) 27 | -------------------------------------------------------------------------------- /tests/test_output.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | from qiskit_qir.translate import to_qir_module 6 | from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister 7 | 8 | import test_utils 9 | 10 | 11 | def test_single_array(): 12 | circuit = QuantumCircuit(3, 3) 13 | circuit.name = "test_single_array" 14 | circuit.h(1) 15 | circuit.s(2) 16 | circuit.t(0) 17 | circuit.measure([0, 1, 2], [2, 0, 1]) 18 | 19 | generated_qir = str(to_qir_module(circuit)[0]).splitlines() 20 | 21 | test_utils.check_attributes(generated_qir, 3, 3) 22 | func = test_utils.get_entry_point_body(generated_qir) 23 | 24 | assert func[0] == test_utils.initialize_call_string() 25 | assert func[1] == test_utils.single_op_call_string("h", 1) 26 | assert func[2] == test_utils.single_op_call_string("s", 2) 27 | assert func[3] == test_utils.single_op_call_string("t", 0) 28 | assert func[4] == test_utils.measure_call_string("mz", 2, 0) 29 | assert func[5] == test_utils.measure_call_string("mz", 0, 1) 30 | assert func[6] == test_utils.measure_call_string("mz", 1, 2) 31 | assert func[7] == test_utils.array_record_output_string(3) 32 | assert func[8] == test_utils.result_record_output_string(2) 33 | assert func[9] == test_utils.result_record_output_string(1) 34 | assert func[10] == test_utils.result_record_output_string(0) 35 | assert func[11] == test_utils.return_string() 36 | assert len(func) == 12 37 | 38 | 39 | def test_no_measure_with_no_registers(): 40 | circuit = QuantumCircuit(1, 0) 41 | circuit.name = "test_no_measure_with_no_registers" 42 | circuit.h(0) 43 | generated_qir = str(to_qir_module(circuit)[0]).splitlines() 44 | 45 | test_utils.check_attributes(generated_qir, 1, 0) 46 | func = test_utils.get_entry_point_body(generated_qir) 47 | 48 | assert func[0] == test_utils.initialize_call_string() 49 | assert func[1] == test_utils.single_op_call_string("h", 0) 50 | assert func[2] == test_utils.return_string() 51 | assert len(func) == 3 52 | 53 | 54 | def test_no_measure_with_register(): 55 | circuit = QuantumCircuit(1, 1) 56 | circuit.name = "test_no_measure_with_register" 57 | circuit.h(0) 58 | generated_qir = str(to_qir_module(circuit)[0]).splitlines() 59 | 60 | test_utils.check_attributes(generated_qir, 1, 1) 61 | func = test_utils.get_entry_point_body(generated_qir) 62 | 63 | assert func[0] == test_utils.initialize_call_string() 64 | assert func[1] == test_utils.single_op_call_string("h", 0) 65 | assert func[2] == test_utils.array_record_output_string(1) 66 | assert func[3] == test_utils.result_record_output_string(0) 67 | assert func[4] == test_utils.return_string() 68 | assert len(func) == 5 69 | 70 | 71 | def test_branching_on_bit_emits_correct_ir(): 72 | qr = QuantumRegister(1, "qreg") 73 | cr = ClassicalRegister(1, "creg") 74 | circuit = QuantumCircuit(qr, cr, name="branching_on_bit_emits_correct_ir") 75 | circuit.x(0) 76 | circuit.measure(0, 0) 77 | circuit.x(0).c_if(cr[0], 1) 78 | 79 | ir = str(to_qir_module(circuit)[0]) 80 | generated_qir = ir.splitlines() 81 | 82 | test_utils.check_attributes(generated_qir, 1, 1) 83 | func = test_utils.get_entry_point_body(generated_qir) 84 | 85 | assert func[0] == test_utils.initialize_call_string() 86 | assert func[1] == test_utils.single_op_call_string("x", 0) 87 | assert func[2] == test_utils.measure_call_string("mz", 0, 0) 88 | assert func[3] == test_utils.equal("0", 0) 89 | assert func[4] == f"br i1 %0, label %then, label %else" 90 | assert func[5] == "" 91 | assert ( 92 | func[6] == f"then: ; preds = %entry" 93 | ) 94 | assert func[7] == test_utils.single_op_call_string("x", 0) 95 | assert func[8] == f"br label %continue" 96 | assert func[9] == "" 97 | assert ( 98 | func[10] 99 | == f"else: ; preds = %entry" 100 | ) 101 | assert func[11] == f"br label %continue" 102 | assert func[12] == "" 103 | assert ( 104 | func[13] 105 | == f"continue: ; preds = %else, %then" 106 | ) 107 | assert func[14] == test_utils.array_record_output_string(1) 108 | assert func[15] == test_utils.result_record_output_string(0) 109 | assert func[16] == test_utils.return_string() 110 | 111 | assert len(func) == 17 112 | 113 | 114 | def test_branching_on_register_with_one_bit_emits_correct_ir(): 115 | qr = QuantumRegister(1, "qreg") 116 | cr = ClassicalRegister(1, "creg") 117 | circuit = QuantumCircuit( 118 | qr, cr, name="branching_on_register_with_one_bit_emits_correct_ir" 119 | ) 120 | circuit.x(0) 121 | circuit.measure(0, 0) 122 | circuit.x(0).c_if(cr, 1) 123 | 124 | ir = str(to_qir_module(circuit)[0]) 125 | generated_qir = ir.splitlines() 126 | 127 | test_utils.check_attributes(generated_qir, 1, 1) 128 | func = test_utils.get_entry_point_body(generated_qir) 129 | 130 | assert func[0] == test_utils.initialize_call_string() 131 | assert func[1] == test_utils.single_op_call_string("x", 0) 132 | assert func[2] == test_utils.measure_call_string("mz", 0, 0) 133 | assert func[3] == test_utils.equal("0", 0) 134 | assert func[4] == f"br i1 %0, label %then, label %else" 135 | assert func[5] == "" 136 | assert ( 137 | func[6] == f"then: ; preds = %entry" 138 | ) 139 | assert func[7] == test_utils.single_op_call_string("x", 0) 140 | assert func[8] == f"br label %continue" 141 | assert func[9] == "" 142 | assert ( 143 | func[10] 144 | == f"else: ; preds = %entry" 145 | ) 146 | assert func[11] == f"br label %continue" 147 | assert func[12] == "" 148 | assert ( 149 | func[13] 150 | == f"continue: ; preds = %else, %then" 151 | ) 152 | assert func[14] == test_utils.array_record_output_string(1) 153 | assert func[15] == test_utils.result_record_output_string(0) 154 | assert func[16] == test_utils.return_string() 155 | 156 | assert len(func) == 17 157 | 158 | 159 | def test_no_measure_without_registers(): 160 | circuit = QuantumCircuit(1) 161 | circuit.name = "test_no_measure_no_registers" 162 | circuit.h(0) 163 | 164 | generated_qir = str(to_qir_module(circuit)[0]).splitlines() 165 | 166 | test_utils.check_attributes(generated_qir, 1, 0) 167 | func = test_utils.get_entry_point_body(generated_qir) 168 | 169 | assert func[0] == test_utils.initialize_call_string() 170 | assert func[1] == test_utils.single_op_call_string("h", 0) 171 | assert func[2] == test_utils.return_string() 172 | assert len(func) == 3 173 | 174 | 175 | def test_measurement_into_multiple_registers_is_mapped_correctly(): 176 | cr0 = ClassicalRegister(2, "first") 177 | cr1 = ClassicalRegister(3, "second") 178 | circuit = QuantumCircuit(5) 179 | circuit.add_register(cr0) 180 | circuit.add_register(cr1) 181 | circuit.name = "measurement_into_multiple_registers" 182 | circuit.h(0) 183 | 184 | circuit.measure([0, 0], [0, 2]) 185 | 186 | generated_qir = str(to_qir_module(circuit)[0]).splitlines() 187 | 188 | test_utils.check_attributes(generated_qir, 5, 5) 189 | func = test_utils.get_entry_point_body(generated_qir) 190 | 191 | assert func[0] == test_utils.initialize_call_string() 192 | assert func[1] == test_utils.single_op_call_string("h", 0) 193 | assert func[2] == test_utils.measure_call_string("mz", 0, 0) 194 | assert func[3] == test_utils.measure_call_string("mz", 2, 0) 195 | assert func[4] == test_utils.array_record_output_string(2) 196 | assert func[5] == test_utils.result_record_output_string(1) 197 | assert func[6] == test_utils.result_record_output_string(0) 198 | assert func[7] == test_utils.array_record_output_string(3) 199 | assert func[8] == test_utils.result_record_output_string(4) 200 | assert func[9] == test_utils.result_record_output_string(3) 201 | assert func[10] == test_utils.result_record_output_string(2) 202 | assert func[11] == test_utils.return_string() 203 | assert len(func) == 12 204 | 205 | 206 | def test_using_static_allocation_is_mapped_correctly(): 207 | circuit = QuantumCircuit(1, 1) 208 | circuit.h(0) 209 | circuit.measure(0, 0) 210 | 211 | ir = str(to_qir_module(circuit)[0]) 212 | generated_qir = ir.splitlines() 213 | 214 | test_utils.check_attributes(generated_qir, 1, 1) 215 | func = test_utils.get_entry_point_body(generated_qir) 216 | 217 | assert func[0] == test_utils.initialize_call_string() 218 | assert func[1] == test_utils.single_op_call_string("h", 0) 219 | assert func[2] == test_utils.measure_call_string("mz", 0, 0) 220 | assert func[3] == test_utils.array_record_output_string(1) 221 | assert func[4] == test_utils.result_record_output_string(0) 222 | assert func[5] == test_utils.return_string() 223 | assert len(func) == 6 224 | 225 | 226 | def test_record_output_when_true_mapped_correctly(): 227 | circuit = QuantumCircuit(1, 1) 228 | circuit.h(0) 229 | circuit.measure(0, 0) 230 | 231 | ir = str(to_qir_module(circuit, record_output=True)[0]) 232 | generated_qir = ir.splitlines() 233 | 234 | test_utils.check_attributes(generated_qir, 1, 1) 235 | func = test_utils.get_entry_point_body(generated_qir) 236 | 237 | assert func[0] == test_utils.initialize_call_string() 238 | assert func[1] == test_utils.single_op_call_string("h", 0) 239 | assert func[2] == test_utils.measure_call_string("mz", 0, 0) 240 | assert func[3] == test_utils.array_record_output_string(1) 241 | assert func[4] == test_utils.result_record_output_string(0) 242 | assert func[5] == test_utils.return_string() 243 | assert len(func) == 6 244 | 245 | 246 | def test_record_output_when_false_mapped_correctly(): 247 | circuit = QuantumCircuit(1, 1) 248 | circuit.h(0) 249 | circuit.measure(0, 0) 250 | 251 | ir = str(to_qir_module(circuit, record_output=False)[0]) 252 | generated_qir = ir.splitlines() 253 | 254 | test_utils.check_attributes(generated_qir, 1, 1) 255 | func = test_utils.get_entry_point_body(generated_qir) 256 | 257 | assert func[0] == test_utils.initialize_call_string() 258 | assert func[1] == test_utils.single_op_call_string("h", 0) 259 | assert func[2] == test_utils.measure_call_string("mz", 0, 0) 260 | assert func[3] == test_utils.return_string() 261 | assert len(func) == 4 262 | 263 | 264 | def test_barrier_default_bypass(): 265 | circuit = QuantumCircuit(1) 266 | circuit.barrier() 267 | circuit.x(0) 268 | 269 | ir = str(to_qir_module(circuit)[0]) 270 | generated_qir = ir.splitlines() 271 | 272 | test_utils.check_attributes(generated_qir, 1, 0) 273 | func = test_utils.get_entry_point_body(generated_qir) 274 | 275 | assert func[0] == test_utils.initialize_call_string() 276 | assert func[1] == test_utils.single_op_call_string("x", 0) 277 | assert func[2] == test_utils.return_string() 278 | assert len(func) == 3 279 | 280 | 281 | def test_barrier_with_qubits_default_bypass(): 282 | circuit = QuantumCircuit(3) 283 | circuit.barrier([2, 0, 1]) 284 | circuit.x(0) 285 | 286 | ir = str(to_qir_module(circuit)[0]) 287 | generated_qir = ir.splitlines() 288 | 289 | test_utils.check_attributes(generated_qir, 3, 0) 290 | func = test_utils.get_entry_point_body(generated_qir) 291 | 292 | assert func[0] == test_utils.initialize_call_string() 293 | assert func[1] == test_utils.single_op_call_string("x", 0) 294 | assert func[2] == test_utils.return_string() 295 | assert len(func) == 3 296 | 297 | 298 | def test_barrier_with_override(): 299 | circuit = QuantumCircuit(1) 300 | circuit.barrier() 301 | 302 | ir = str(to_qir_module(circuit, emit_barrier_calls=True)[0]) 303 | generated_qir = ir.splitlines() 304 | 305 | test_utils.check_attributes(generated_qir, 1, 0) 306 | func = test_utils.get_entry_point_body(generated_qir) 307 | 308 | assert func[0] == test_utils.initialize_call_string() 309 | assert func[1] == test_utils.generic_op_call_string("barrier", []) 310 | assert func[2] == test_utils.return_string() 311 | assert len(func) == 3 312 | 313 | 314 | def test_barrier_with_qubits_with_override(): 315 | circuit = QuantumCircuit(3) 316 | circuit.barrier([2, 0, 1]) 317 | 318 | ir = str(to_qir_module(circuit, emit_barrier_calls=True)[0]) 319 | generated_qir = ir.splitlines() 320 | 321 | test_utils.check_attributes(generated_qir, 3, 0) 322 | func = test_utils.get_entry_point_body(generated_qir) 323 | 324 | assert func[0] == test_utils.initialize_call_string() 325 | assert func[1] == test_utils.generic_op_call_string("barrier", []) 326 | assert func[2] == test_utils.return_string() 327 | assert len(func) == 3 328 | 329 | 330 | def test_swap(): 331 | circuit = QuantumCircuit(3) 332 | circuit.swap(2, 0) 333 | 334 | ir = str(to_qir_module(circuit)[0]) 335 | generated_qir = ir.splitlines() 336 | 337 | test_utils.check_attributes(generated_qir, 3, 0) 338 | func = test_utils.get_entry_point_body(generated_qir) 339 | 340 | assert func[0] == test_utils.initialize_call_string() 341 | assert func[1] == test_utils.double_op_call_string("swap", 2, 0) 342 | assert func[2] == test_utils.return_string() 343 | assert len(func) == 3 344 | 345 | 346 | def test_ccx(): 347 | circuit = QuantumCircuit(3) 348 | circuit.ccx(2, 0, 1) 349 | 350 | ir = str(to_qir_module(circuit)[0]) 351 | generated_qir = ir.splitlines() 352 | 353 | test_utils.check_attributes(generated_qir, 3, 0) 354 | func = test_utils.get_entry_point_body(generated_qir) 355 | 356 | assert func[0] == test_utils.initialize_call_string() 357 | assert func[1] == test_utils.generic_op_call_string("ccx", [2, 0, 1]) 358 | assert func[2] == test_utils.return_string() 359 | assert len(func) == 3 360 | -------------------------------------------------------------------------------- /tests/test_qiskit_qir.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | from datetime import datetime 6 | from pathlib import Path 7 | import pytest 8 | import logging 9 | 10 | from qiskit_qir.elements import QiskitModule 11 | from qiskit_qir.visitor import BasicQisVisitor 12 | from qiskit_qir.translate import to_qir_module 13 | 14 | from test_circuits import core_tests, noop_tests 15 | from test_circuits.control_flow_circuits import cf_fixtures 16 | from test_circuits.basic_gates import ( 17 | single_op_tests, 18 | adj_op_tests, 19 | rotation_tests, 20 | double_op_tests, 21 | triple_op_tests, 22 | measurement_tests, 23 | ) 24 | 25 | import test_utils 26 | 27 | _log = logging.getLogger(__name__) 28 | _test_output_dir = Path(f"test_output.{datetime.now().strftime('%Y%m%d_%H%M')}") 29 | if _log.isEnabledFor(logging.DEBUG) and not _test_output_dir.exists(): 30 | _test_output_dir.mkdir() 31 | 32 | 33 | @pytest.mark.parametrize("circuit_name", core_tests) 34 | def test_visitor(circuit_name, request): 35 | circuit = request.getfixturevalue(circuit_name) 36 | module = QiskitModule.from_quantum_circuit(circuit=circuit) 37 | visitor = BasicQisVisitor() 38 | module.accept(visitor) 39 | generated_ir = visitor.ir() 40 | _log.debug(generated_ir) 41 | assert generated_ir is not None 42 | 43 | 44 | @pytest.mark.parametrize("circuit_name", core_tests) 45 | def test_to_qir_string(circuit_name, request): 46 | circuit = request.getfixturevalue(circuit_name) 47 | generated_ir = str(to_qir_module(circuit)[0]) 48 | assert generated_ir is not None 49 | if _log.isEnabledFor(logging.DEBUG): 50 | qasm_path = _test_output_dir.joinpath(circuit_name + ".qasm") 51 | circuit.qasm(filename=str(qasm_path)) 52 | qir_path = _test_output_dir.joinpath(circuit_name + ".ll") 53 | qir_path.write_text(generated_ir) 54 | 55 | 56 | @pytest.mark.parametrize("circuit_name", core_tests) 57 | def test_to_qir_bitcode(circuit_name, request): 58 | circuit = request.getfixturevalue(circuit_name) 59 | generated_bitcode = to_qir_module(circuit)[0].bitcode 60 | assert generated_bitcode is not None 61 | 62 | 63 | @pytest.mark.parametrize("circuit_name", noop_tests) 64 | def test_noop_gates(circuit_name, request): 65 | circuit = request.getfixturevalue(circuit_name) 66 | module = QiskitModule.from_quantum_circuit(circuit=circuit) 67 | visitor = BasicQisVisitor() 68 | module.accept(visitor) 69 | generated_ir = visitor.ir() 70 | _log.debug(generated_ir) 71 | assert generated_ir is not None 72 | 73 | 74 | @pytest.mark.xfail(Reason="OpenQASM 3.0-style control flow is not supported yet") 75 | @pytest.mark.parametrize("circuit_name", cf_fixtures) 76 | def test_control_flow(circuit_name, request): 77 | circuit = request.getfixturevalue(circuit_name) 78 | generated_ir = str(to_qir_module(circuit)[0]) 79 | assert generated_ir is not None 80 | if _log.isEnabledFor(logging.DEBUG): 81 | qasm_path = _test_output_dir.joinpath(circuit_name + ".qasm") 82 | circuit.qasm(filename=str(qasm_path)) 83 | qir_path = _test_output_dir.joinpath(circuit_name + ".ll") 84 | qir_path.write_text(generated_ir) 85 | 86 | 87 | @pytest.mark.parametrize("circuit_name", single_op_tests) 88 | def test_single_qubit_gates(circuit_name, request): 89 | qir_op, circuit = request.getfixturevalue(circuit_name) 90 | generated_qir = str(to_qir_module(circuit)[0]).splitlines() 91 | test_utils.check_attributes(generated_qir, 1, 0) 92 | func = test_utils.get_entry_point_body(generated_qir) 93 | assert func[0] == test_utils.initialize_call_string() 94 | assert func[1] == test_utils.single_op_call_string(qir_op, 0) 95 | assert func[2] == test_utils.return_string() 96 | assert len(func) == 3 97 | 98 | 99 | @pytest.mark.parametrize("circuit_name", adj_op_tests) 100 | def test_adj_gates(circuit_name, request): 101 | qir_op, circuit = request.getfixturevalue(circuit_name) 102 | generated_qir = str(to_qir_module(circuit)[0]).splitlines() 103 | test_utils.check_attributes(generated_qir, 1, 0) 104 | func = test_utils.get_entry_point_body(generated_qir) 105 | assert func[0] == test_utils.initialize_call_string() 106 | assert func[1] == test_utils.adj_op_call_string(qir_op, 0) 107 | assert func[2] == test_utils.return_string() 108 | assert len(func) == 3 109 | 110 | 111 | @pytest.mark.parametrize("circuit_name", rotation_tests) 112 | def test_rotation_gates(circuit_name, request): 113 | qir_op, circuit = request.getfixturevalue(circuit_name) 114 | generated_qir = str(to_qir_module(circuit)[0]).splitlines() 115 | test_utils.check_attributes(generated_qir, 1, 0) 116 | func = test_utils.get_entry_point_body(generated_qir) 117 | assert func[0] == test_utils.initialize_call_string() 118 | assert func[1] == test_utils.rotation_call_string(qir_op, 0.5, 0) 119 | assert func[2] == test_utils.return_string() 120 | assert len(func) == 3 121 | 122 | 123 | @pytest.mark.parametrize("circuit_name", double_op_tests) 124 | def test_double_qubit_gates(circuit_name, request): 125 | qir_op, circuit = request.getfixturevalue(circuit_name) 126 | generated_qir = str(to_qir_module(circuit)[0]).splitlines() 127 | test_utils.check_attributes(generated_qir, 2, 0) 128 | func = test_utils.get_entry_point_body(generated_qir) 129 | assert func[0] == test_utils.initialize_call_string() 130 | assert func[1] == test_utils.double_op_call_string(qir_op, 0, 1) 131 | assert func[2] == test_utils.return_string() 132 | assert len(func) == 3 133 | 134 | 135 | @pytest.mark.parametrize("circuit_name", triple_op_tests) 136 | def test_triple_qubit_gates(circuit_name, request): 137 | qir_op, circuit = request.getfixturevalue(circuit_name) 138 | generated_qir = str(to_qir_module(circuit)[0]).splitlines() 139 | test_utils.check_attributes(generated_qir, 3, 0) 140 | func = test_utils.get_entry_point_body(generated_qir) 141 | assert func[0] == test_utils.initialize_call_string() 142 | assert func[1] == test_utils.generic_op_call_string(qir_op, [2, 0, 1]) 143 | assert func[2] == test_utils.return_string() 144 | assert len(func) == 3 145 | 146 | 147 | @pytest.mark.parametrize("circuit_name", measurement_tests) 148 | def test_measurement(circuit_name, request): 149 | qir_op, circuit = request.getfixturevalue(circuit_name) 150 | generated_qir = str(to_qir_module(circuit)[0]).splitlines() 151 | test_utils.check_attributes(generated_qir, 1, 1) 152 | func = test_utils.get_entry_point_body(generated_qir) 153 | 154 | assert func[0] == test_utils.initialize_call_string() 155 | assert func[1] == test_utils.measure_call_string(qir_op, 0, 0) 156 | assert func[2] == test_utils.array_record_output_string(1) 157 | assert func[3] == test_utils.result_record_output_string(0) 158 | assert func[4] == test_utils.return_string() 159 | assert len(func) == 5 160 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | ## 2 | # Copyright (c) Microsoft Corporation. 3 | # Licensed under the MIT License. 4 | ## 5 | 6 | from typing import List 7 | from pyqir import is_entry_point, Context, Module, Function 8 | 9 | 10 | def _qubit_string(qubit: int) -> str: 11 | if qubit == 0: 12 | return "%Qubit* null" 13 | else: 14 | return f"%Qubit* inttoptr (i64 {qubit} to %Qubit*)" 15 | 16 | 17 | def _result_string(res: int) -> str: 18 | if res == 0: 19 | return "%Result* null" 20 | else: 21 | return f"%Result* inttoptr (i64 {res} to %Result*)" 22 | 23 | 24 | def initialize_call_string() -> str: 25 | return "call void @__quantum__rt__initialize(i8* null)" 26 | 27 | 28 | def single_op_call_string(name: str, qb: int) -> str: 29 | return f"call void @__quantum__qis__{name}__body({_qubit_string(qb)})" 30 | 31 | 32 | def adj_op_call_string(name: str, qb: int) -> str: 33 | return f"call void @__quantum__qis__{name}__adj({_qubit_string(qb)})" 34 | 35 | 36 | def double_op_call_string(name: str, qb1: int, qb2: int) -> str: 37 | return f"call void @__quantum__qis__{name}__body({_qubit_string(qb1)}, {_qubit_string(qb2)})" 38 | 39 | 40 | def rotation_call_string(name: str, theta: float, qb: int) -> str: 41 | return f"call void @__quantum__qis__{name}__body(double {theta:#e}, {_qubit_string(qb)})" 42 | 43 | 44 | def measure_call_string(name: str, res: str, qb: int) -> str: 45 | return f"call void @__quantum__qis__{name}__body({_qubit_string(qb)}, {_result_string(res)})" 46 | 47 | 48 | def equal(var: str, res: str): 49 | return f"%{var} = call i1 @__quantum__qis__read_result__body({_result_string(res)})" 50 | 51 | 52 | def generic_op_call_string(name: str, qbs: List[int]) -> str: 53 | args = ", ".join(_qubit_string(qb) for qb in qbs) 54 | return f"call void @__quantum__qis__{name}__body({args})" 55 | 56 | 57 | def return_string() -> str: 58 | return "ret void" 59 | 60 | 61 | def array_record_output_string(num_elements: int) -> str: 62 | return ( 63 | f"call void @__quantum__rt__array_record_output(i64 {num_elements}, i8* null)" 64 | ) 65 | 66 | 67 | def result_record_output_string(res: str) -> str: 68 | return f"call void @__quantum__rt__result_record_output({_result_string(res)}, i8* null)" 69 | 70 | 71 | # Returns the method body with: 72 | # - leading spaces trimmed 73 | # - first label skipped 74 | # - signature and closing braces removed 75 | def get_entry_point_body(qir: List[str]) -> List[str]: 76 | joined = "\n".join(qir) 77 | mod = Module.from_ir(Context(), joined) 78 | func = next(filter(is_entry_point, mod.functions)) 79 | assert func is not None, "No main function found" 80 | lines = str(func).splitlines()[2:-1] 81 | return list(map(lambda line: line.strip(), lines)) 82 | 83 | 84 | def get_entry_point(mod: Module) -> Function: 85 | func = next(filter(is_entry_point, mod.functions)) 86 | assert func is not None, "No main function found" 87 | return func 88 | 89 | 90 | def check_attributes( 91 | qir: List[str], expected_qubits: int = 0, expected_results: int = 0 92 | ) -> None: 93 | x = "\n".join(qir) 94 | mod = Module.from_ir(Context(), x) 95 | func = next(filter(is_entry_point, mod.functions)) 96 | 97 | check_attributes_on_entrypoint(func, expected_qubits, expected_results) 98 | 99 | 100 | def check_attributes_on_entrypoint( 101 | func: Function, expected_qubits: int = 0, expected_results: int = 0 102 | ) -> None: 103 | actual_qubits = -1 104 | actual_results = -1 105 | from pyqir import required_num_qubits, required_num_results 106 | 107 | actual_qubits = required_num_qubits(func) 108 | actual_results = required_num_results(func) 109 | assert ( 110 | expected_qubits == actual_qubits 111 | ), f"Incorrect qubit count: {expected_qubits} expected, {actual_qubits} actual" 112 | 113 | assert ( 114 | expected_results == actual_results 115 | ), f"Incorrect result count: {expected_results} expected, {actual_results} actual" 116 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38, py39, py310, py311, py312, black 3 | 4 | [travis] 5 | python = 6 | 3.12: py312 7 | 3.11: py311 8 | 3.10: py310 9 | 3.9: py39 10 | 3.8: py38 11 | 12 | [testenv:black] 13 | basepython = python 14 | deps = black 15 | commands = black --check src tests 16 | 17 | [testenv] 18 | setenv = 19 | PYTHONPATH = {toxinidir} 20 | deps = 21 | -r{toxinidir}/requirements_dev.txt 22 | ; If you want to make tox run the tests with the same versions, create a 23 | ; requirements.txt with the pinned versions and uncomment the following line: 24 | ; -r{toxinidir}/requirements.txt 25 | commands = 26 | pip install -U pip 27 | pytest --basetemp={envtmpdir} 28 | --------------------------------------------------------------------------------