├── .circleci └── config.yml ├── .coveragerc ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile ├── README.rst ├── api_ref.rst ├── circuit.rst ├── conf.py ├── index.rst ├── make.bat ├── operations.rst ├── qir.rst ├── registers.rst ├── requirements.txt ├── simulator.rst └── tools.rst ├── dwave └── gate │ ├── __init__.py │ ├── circuit.py │ ├── mixedproperty.py │ ├── operations │ ├── __init__.py │ ├── base.py │ └── operations.py │ ├── primitives.py │ ├── qir │ ├── __init__.py │ ├── compiler.py │ ├── instructions.py │ └── loader.py │ ├── registers │ ├── __init__.py │ ├── cyregister.pxd │ ├── cyregister.pyi │ ├── cyregister.pyx │ └── registers.py │ ├── simulator │ ├── __init__.py │ ├── operation_generation.py │ └── simulator.pyx │ └── tools │ ├── __init__.py │ ├── counters.py │ └── unitary.py ├── examples ├── 0_circuit.py ├── 1_operations.py ├── 2_templates.py ├── 3_parametric_circuits.py ├── 4_simulator.py └── 5_measurements.py ├── make.bat ├── makefile ├── pyproject.toml ├── releasenotes └── notes │ ├── add-get-qubit-method-c854f57d84719a8f.yaml │ ├── add-measurements-c548833d86d764a8.yaml │ ├── context-returns-namedtuple-83bda2334a7fb8d1.yaml │ ├── deprecate-python3.7--102cd953fb6e9a09.yaml │ ├── feature-python312-cython3-f79ec6b10494fdc3.yaml │ ├── feature-qir-compiler-58332c0019b51ae4.yaml │ ├── fix-cond-not-propagating-5dd63d14836aa27e.yaml │ ├── fix-measurement-storage-issue-ab52cc05d0802876.yaml │ ├── fix-open-issues-6b777eed9cf72188.yaml │ ├── fix-operation-call-return-0d522322ccf0f217.yaml │ ├── fix-qir-load-midfunc-return-b0f952734e3c6f2a.yaml │ ├── fix-sample-circular-import-86ae100745b87464.yaml │ ├── pyqir-support-a682839ae2ccc0c5.yaml │ ├── python-3.9-3.13-9b276ed8d1edfdf5.yaml │ ├── support-numpy-2.0-d9eae4d898fe217e.yaml │ ├── switch-to-native-namespaces-e8a771fc8003599f.yaml │ ├── update-makefiles-cd19366de0add7d7.yaml │ ├── upgrade-circuit-call-bits-f308cc926fa3bce3.yaml │ ├── upgrade-multi-qubit-sampling-89e654a179975106.yaml │ └── upgrade-simulator-return-90e29e50768b7112.yaml ├── requirements.txt ├── requirements_dev.txt ├── setup.py └── tests ├── conftest.py ├── test_circuit.py ├── test_io └── test_openqasm.py ├── test_mixedproperty.py ├── test_operations ├── test_base.py └── test_operations.py ├── test_primitives.py ├── test_qir ├── test_compile.py ├── test_instructions.py └── test_load.py ├── test_registers.py ├── test_simulator ├── test_measurements.py └── test_simulator.py └── test_tools ├── test_counters.py └── test_unitary.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | win: circleci/windows@5.0 5 | macos: circleci/macos@2.4 6 | 7 | commands: 8 | run-cibuildwheel: 9 | parameters: 10 | cibw-version: 11 | type: string 12 | default: 2.21.2 13 | steps: 14 | - run: 15 | name: run cibuildwheel 16 | shell: bash -eo pipefail 17 | command: | 18 | if [[ $OS == Windows_NT ]]; then 19 | python -m pip install --user cibuildwheel==<< parameters.cibw-version >> 20 | python -m cibuildwheel --output-dir dist 21 | else 22 | python3 -m pip install --user cibuildwheel==<< parameters.cibw-version >> 23 | python3 -m cibuildwheel --output-dir dist 24 | fi 25 | 26 | - store_artifacts: &store-artifacts 27 | path: ./dist 28 | - persist_to_workspace: &persist-to-workspace 29 | root: ./dist/ 30 | paths: . 31 | 32 | environment: &global-environment 33 | PIP_PROGRESS_BAR: 'off' 34 | 35 | jobs: 36 | build-linux: 37 | parameters: 38 | python-version: 39 | type: string 40 | 41 | docker: 42 | - image: cimg/python:3.12 43 | 44 | environment: 45 | <<: *global-environment 46 | CIBW_PROJECT_REQUIRES_PYTHON: ~=<< parameters.python-version>> 47 | CIBW_ARCHS_LINUX: x86_64 48 | 49 | steps: 50 | - checkout 51 | - setup_remote_docker 52 | - run-cibuildwheel 53 | 54 | build-linux-aarch64: 55 | parameters: 56 | python-version: 57 | type: string 58 | 59 | machine: 60 | image: default 61 | 62 | resource_class: arm.medium 63 | 64 | environment: 65 | <<: *global-environment 66 | CIBW_PROJECT_REQUIRES_PYTHON: ~=<< parameters.python-version>> 67 | CIBW_ARCHS_LINUX: aarch64 68 | 69 | steps: 70 | - checkout 71 | - run-cibuildwheel 72 | 73 | build-osx: 74 | parameters: 75 | python-version: 76 | type: string 77 | xcode: 78 | type: string 79 | default: "14.3.0" 80 | 81 | resource_class: macos.m1.medium.gen1 82 | 83 | macos: 84 | xcode: << parameters.xcode >> 85 | 86 | environment: 87 | <<: *global-environment 88 | CIBW_PROJECT_REQUIRES_PYTHON: ~=<< parameters.python-version>> 89 | 90 | steps: 91 | - checkout 92 | - macos/install-rosetta 93 | - run-cibuildwheel 94 | 95 | build-sdist: 96 | docker: 97 | - image: cimg/python:3.12 98 | 99 | steps: 100 | - checkout 101 | - run: 102 | name: build sdist 103 | command: | 104 | python -m venv env 105 | . env/bin/activate 106 | pip install -r requirements.txt 107 | pip install setuptools --upgrade 108 | python dwave/gate/simulator/operation_generation.py 109 | python setup.py sdist -d ./dist 110 | - store_artifacts: *store-artifacts 111 | - persist_to_workspace: *persist-to-workspace 112 | 113 | build-windows: 114 | parameters: 115 | python-version: 116 | type: string 117 | 118 | executor: 119 | name: win/default 120 | 121 | environment: 122 | <<: *global-environment 123 | CIBW_PROJECT_REQUIRES_PYTHON: ~=<< parameters.python-version>> 124 | CIBW_ARCHS_WINDOWS: AMD64 125 | 126 | steps: 127 | - checkout 128 | - run-cibuildwheel 129 | 130 | deploy-all: 131 | docker: 132 | - image: cimg/python:3.12 133 | 134 | steps: 135 | - attach_workspace: 136 | at: dist 137 | 138 | - store_artifacts: 139 | path: ./dist 140 | 141 | - run: 142 | name: deploy 143 | command: | 144 | python -m venv env 145 | . env/bin/activate 146 | python -m pip install twine 147 | twine upload -u "$PYPI_USERNAME" -p "$PYPI_PASSWORD" --skip-existing ./dist/* 148 | 149 | # we could do this as part of the various test jobs but getting the pathing 150 | # and configuration to work correctly is a pain. And since there is not 151 | # significant different between the linux/osx/windows code I think it 152 | # suffices to just do it once 153 | test-codecov: 154 | docker: 155 | - image: cimg/python:3.12 156 | 157 | steps: 158 | - checkout 159 | - run: 160 | name: install dependencies 161 | command: | 162 | python -m venv env 163 | . env/bin/activate 164 | pip install -r requirements.txt 165 | pip install -r requirements_dev.txt 166 | - run: &unix-build 167 | name: build 168 | command: | 169 | . env/bin/activate 170 | python dwave/gate/simulator/operation_generation.py 171 | pip install . 172 | - run: 173 | name: run coverage 174 | command: | 175 | . env/bin/activate 176 | pytest tests/ --cov=dwave.gate 177 | - run: 178 | name: codecov 179 | command: | 180 | . env/bin/activate 181 | pip install codecov 182 | codecov 183 | 184 | test-sdist: 185 | docker: 186 | - image: cimg/python:3.12 187 | 188 | steps: 189 | - checkout 190 | - attach_workspace: 191 | at: dist 192 | - run: 193 | name: install from sdist 194 | command: | 195 | python -m venv env 196 | . env/bin/activate 197 | pip install dist/dwave_gate*.tar.gz 198 | - run: 199 | name: run tests 200 | command: | 201 | . env/bin/activate 202 | pip install -r requirements_dev.txt 203 | pytest tests/ 204 | 205 | test-dependencies: 206 | parameters: 207 | python-version: 208 | type: string 209 | dependency-versions: 210 | type: string 211 | 212 | docker: 213 | - image: python:<< parameters.python-version >>-slim 214 | 215 | steps: 216 | - checkout 217 | - attach_workspace: 218 | at: dist 219 | - run: 220 | name: install 221 | command: | 222 | python -m venv env 223 | . env/bin/activate 224 | pip install "<< parameters.dependency-versions >>" --upgrade --only-binary=numpy 225 | pip install dwave-gate --no-index -f dist/ --no-deps --force-reinstall 226 | - run: &unix-run-tests 227 | name: run tests 228 | command: | 229 | . env/bin/activate 230 | pip install -r requirements_dev.txt 231 | pytest tests/ 232 | workflows: 233 | tests: 234 | jobs: 235 | - build-linux: &build 236 | matrix: 237 | parameters: 238 | python-version: &python-versions [3.9.19, 3.10.14, 3.11.9, 3.12.4, 3.13.0] 239 | - build-linux-aarch64: *build 240 | - build-sdist 241 | - build-osx: *build 242 | - build-windows: *build 243 | - test-codecov 244 | - test-dependencies: 245 | name: test-dependencies - << matrix.dependency-versions >> - py << matrix.python-version >> 246 | requires: 247 | - build-linux 248 | matrix: 249 | parameters: 250 | # test the lowest and highest for each dependency 251 | dependency-versions: [ 252 | numpy==1.24.4, 253 | numpy 254 | ] 255 | python-version: *python-versions 256 | exclude: 257 | - dependency-versions: numpy==1.24.4 258 | python-version: 3.12.4 259 | - dependency-versions: numpy==1.24.4 260 | python-version: 3.13.0 261 | - test-sdist: 262 | requires: 263 | - build-sdist 264 | deploy: 265 | jobs: 266 | - build-linux: &deploy-build 267 | <<: *build 268 | filters: 269 | tags: 270 | only: /^[0-9]+(\.[0-9]+)*((\.dev|rc)([0-9]+)?)?$/ 271 | branches: 272 | ignore: /.*/ 273 | - build-linux-aarch64: *deploy-build 274 | - build-osx: *deploy-build 275 | - build-sdist: 276 | filters: 277 | tags: 278 | only: /^[0-9]+(\.[0-9]+)*((\.dev|rc)([0-9]+)?)?$/ 279 | branches: 280 | ignore: /.*/ 281 | - build-windows: *deploy-build 282 | - deploy-all: 283 | filters: 284 | tags: 285 | only: /^[0-9]+(\.[0-9]+)*((\.dev|rc)([0-9]+)?)?$/ 286 | branches: 287 | ignore: /.*/ 288 | requires: 289 | - build-linux 290 | - build-linux-aarch64 291 | - build-osx 292 | - build-sdist 293 | - build-windows 294 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | plugins = Cython.Coverage 4 | source = dwave.gate 5 | omit = 6 | */dwave/gate/simulator/operation_generation.py 7 | tests/* 8 | 9 | [report] 10 | include_namespace_packages = True 11 | exclude_lines = 12 | pragma: no cover 13 | # Don't complain if tests don't hit defensive assertion code 14 | raise AssertionError 15 | raise NotImplementedError 16 | 17 | # Ignore abstract methods, they aren't run 18 | @(abc\.)?abstractmethod 19 | @(abc\.)?abstractproperty 20 | 21 | # Ignore code for type checking 22 | if TYPE_CHECKING 23 | @(typing\.)?overload 24 | 25 | ignore_errors = True 26 | precision = 2 27 | 28 | skip_covered = True 29 | skip_empty = True 30 | 31 | [html] 32 | directory = coverage_html 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Auto-generated files 2 | **/simulator/ops.* 3 | **/simulator/simulator.cpp 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | *.so 10 | cyregister.cpp 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | worktrees/ 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | coverage_html/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | cov.xml 45 | *.cover 46 | .hypothesis/ 47 | *.lcov 48 | 49 | # Sphinx documentation 50 | docs/_build/ 51 | 52 | # Jupyter Notebook 53 | .ipynb_checkpoints 54 | 55 | # pyenv 56 | .python-version 57 | 58 | # dotenv 59 | .env 60 | 61 | # virtualenv 62 | .venv 63 | venv/ 64 | ENV/ 65 | 66 | # mypy 67 | .mypy_cache/ 68 | 69 | # IDEs/editors 70 | .vscode/ 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright 2022 D-Wave Systems Inc. 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include pyproject.toml 2 | recursive-include dwave/gate/ *.cpp *.h *.hpp *.pyx *.pxd *.pxi 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://img.shields.io/pypi/v/dwave-gate.svg 2 | :target: https://pypi.org/project/dwave-gate 3 | 4 | .. image:: https://img.shields.io/pypi/pyversions/dwave-gate.svg 5 | :target: https://pypi.org/project/dwave-gate 6 | 7 | .. image:: https://circleci.com/gh/dwavesystems/dwave-gate.svg?style=svg 8 | :target: https://circleci.com/gh/dwavesystems/dwave-gate 9 | 10 | .. image:: https://codecov.io/gh/dwavesystems/dwave-gate/branch/main/graph/badge.svg 11 | :target: https://codecov.io/gh/dwavesystems/dwave-gate 12 | 13 | ========== 14 | dwave-gate 15 | ========== 16 | 17 | .. start_gate_about 18 | 19 | ``dwave-gate`` is a software package for constructing, modifying and running 20 | quantum circuits on the included simulator. It provides a set of tools that 21 | enables you to: 22 | 23 | * Construct quantum circuits using an intuitive context-manager interface. 24 | 25 | * Utilize a comprehensive library of quantum gates with simple access to 26 | matrix representations, various decompositions, and more. 27 | 28 | * Simulate circuits on a performant (C++) state-vector simulator. 29 | 30 | * Easily create your own quantum gates and templates. Any circuit can be 31 | either directly applied in another circuit or converted into a quantum 32 | operation. 33 | 34 | This example uses the ``dwave.gate.Circuit`` object's context manager to append 35 | operations to a two-qubit circuit. 36 | 37 | >>> import dwave.gate.operations as ops 38 | >>> from dwave.gate import Circuit 39 | 40 | >>> circuit = Circuit(2) 41 | 42 | >>> with circuit.context as (q, c): # doctest: +SKIP 43 | ... ops.X(q[0]) 44 | ... ops.Hadamard(q[1]) 45 | ... ops.CZ(q[0], q[1]) 46 | ... ops.Hadamard(q[1]) 47 | 48 | You can run the ``dwave.gate.simulator`` simulator on such circuits, 49 | 50 | >>> from dwave.gate.simulator import simulate 51 | >>> simulate(circuit) 52 | 53 | and then access the resulting state via the state attribute. 54 | 55 | >>> circuit.state # doctest: +SKIP 56 | array([0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j]) 57 | 58 | .. end_gate_about 59 | 60 | Installation 61 | ============ 62 | 63 | The simplest way to install ``dwave-gate`` is from 64 | `PyPI `_: 65 | 66 | .. code-block:: bash 67 | 68 | pip install dwave-gate 69 | 70 | It can also be installed from source by cloning this GitHub repository and 71 | running: 72 | 73 | .. code-block:: bash 74 | 75 | make install 76 | 77 | The makefile will also simplify running tests (``make test``), coverage 78 | (``make coverage``), documentation (``make docs``), as well as formatting 79 | (``make format``) the code using the `Black `_ 80 | formatter (set to a line-length of 100) and 81 | `isort `_. It's available on both Unix as well 82 | as Windows systems, via the `make.bat` batch file. 83 | 84 | Alternatively, the package can be built and installed in development mode using 85 | Python and pip. The simulator operations would need to be generated first by 86 | executing `operation_generation.py`, found in `dwave/gate/simulator`. 87 | 88 | .. code-block:: bash 89 | 90 | python setup.py build_ext --inplace 91 | pip install -e . 92 | 93 | Tests and coverage can be run using Pytest. 94 | 95 | .. code-block:: bash 96 | 97 | python -m pytest tests/ --cov=dwave.gate 98 | 99 | .. note:: 100 | 101 | For the QIR compiler and loader to work the PyQIR (v0.9.0) is required. It 102 | can be installed manually with ``pip install pyqir==0.9.0`` or as an 103 | optional dependency: 104 | 105 | .. code-block:: bash 106 | 107 | pip install dwave-gate[qir] 108 | 109 | 110 | License 111 | ======= 112 | 113 | Released under the Apache License 2.0. See LICENSE file. 114 | 115 | 116 | Contributing 117 | ============ 118 | 119 | Ocean's `contributing guide `_ 120 | has guidelines for contributing to Ocean packages. 121 | 122 | Release Notes 123 | ------------- 124 | 125 | ``dwave-gate`` uses `reno `_ to manage its 126 | release notes. 127 | 128 | When making a contribution to ``dwave-gate`` that will affect users, create a 129 | new release note file by running 130 | 131 | .. code-block:: bash 132 | 133 | reno new your-short-descriptor-here 134 | 135 | You can then edit the file created under ``releasenotes/notes/``. Remove any 136 | sections not relevant to your changes. Commit the file along with your changes. 137 | 138 | See reno's 139 | `user guide `_ for 140 | details. 141 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 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/README.rst: -------------------------------------------------------------------------------- 1 | ../README.rst -------------------------------------------------------------------------------- /docs/api_ref.rst: -------------------------------------------------------------------------------- 1 | .. _gate_api_ref: 2 | 3 | ============= 4 | API Reference 5 | ============= 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | circuit 11 | operations 12 | registers 13 | simulator 14 | tools 15 | qir 16 | -------------------------------------------------------------------------------- /docs/circuit.rst: -------------------------------------------------------------------------------- 1 | .. _gate_circuits: 2 | 3 | Circuits 4 | ======== 5 | 6 | Circuit Module 7 | -------------- 8 | 9 | .. automodule:: dwave.gate.circuit 10 | :members: 11 | :undoc-members: 12 | :show-inheritance: 13 | 14 | Primitives Module 15 | ----------------- 16 | 17 | .. automodule:: dwave.gate.primitives 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | project = 'dwave-gate' 8 | copyright = '2022, D-Wave Systems Inc.' 9 | author = 'D-Wave Systems Inc.' 10 | release = '2022' 11 | 12 | # -- General configuration --------------------------------------------------- 13 | 14 | extensions = [ 15 | 'reno.sphinxext', 16 | 'sphinx.ext.autodoc', 17 | 'sphinx.ext.napoleon', # must be loaded before 'sphinx_autodoc_typehints' 18 | 'sphinx_autodoc_typehints', 19 | 'sphinx.ext.autosummary', 20 | 'sphinx.ext.todo', 21 | 'sphinx.ext.intersphinx', 22 | 'sphinx.ext.doctest', 23 | ] 24 | 25 | templates_path = ['_templates'] 26 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'README.rst'] 27 | 28 | autodoc_member_order = 'bysource' 29 | typehints_use_rtype = False # avoids duplicate return types 30 | napoleon_use_rtype = False 31 | typehints_defaults = 'comma' 32 | 33 | # -- Options for HTML output ------------------------------------------------- 34 | 35 | # -- Options for HTML output ---------------------------------------------- 36 | html_theme = "pydata_sphinx_theme" 37 | html_theme_options = { 38 | "collapse_navigation": True, 39 | "show_prev_next": False, 40 | } 41 | html_sidebars = {"**": ["search-field", "sidebar-nav-bs"]} # remove ads 42 | 43 | intersphinx_mapping = {'python': ('https://docs.python.org/3', None), 44 | 'numpy': ('https://numpy.org/doc/stable/', None), 45 | 'networkx': ('https://networkx.org/documentation/stable/', None), 46 | 'dwave': ('https://docs.dwavequantum.com/en/latest/', None), 47 | } -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. _index_gate: 2 | 3 | ========== 4 | dwave-gate 5 | ========== 6 | 7 | .. toctree:: 8 | :caption: Reference documentation for dwave-gate: 9 | :maxdepth: 1 10 | 11 | api_ref 12 | 13 | About dwave-gate 14 | ================ 15 | 16 | .. include:: README.rst 17 | :start-after: start_gate_about 18 | :end-before: end_gate_about 19 | 20 | Usage Information 21 | ================= 22 | 23 | .. todo:: add the intersphinx prefix to enable these links for self builds 24 | 25 | * :ref:`index_concepts` for terminology 26 | * :ref:`qpu_gate_model_intro` -------------------------------------------------------------------------------- /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=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/operations.rst: -------------------------------------------------------------------------------- 1 | .. _gate_operations: 2 | 3 | Operations 4 | ========== 5 | 6 | .. automodule:: dwave.gate.operations 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | 11 | Base Module 12 | ----------- 13 | 14 | .. automodule:: dwave.gate.operations.base 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | 19 | Operations Module 20 | ----------------- 21 | 22 | .. automodule:: dwave.gate.operations.operations 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | -------------------------------------------------------------------------------- /docs/qir.rst: -------------------------------------------------------------------------------- 1 | .. _gate_qir: 2 | 3 | QIR 4 | === 5 | 6 | .. automodule:: dwave.gate.qir 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | 11 | Compiler Module 12 | --------------- 13 | 14 | .. automodule:: dwave.gate.qir.compiler 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | 19 | Instructions Module 20 | ------------------- 21 | 22 | .. automodule:: dwave.gate.qir.instructions 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | 27 | Loader Module 28 | ------------- 29 | 30 | .. automodule:: dwave.gate.qir.loader 31 | :members: 32 | :undoc-members: 33 | :show-inheritance: 34 | -------------------------------------------------------------------------------- /docs/registers.rst: -------------------------------------------------------------------------------- 1 | .. _gate_registers: 2 | 3 | Registers 4 | ========= 5 | 6 | .. automodule:: dwave.gate.registers 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | 11 | Registers Module 12 | ---------------- 13 | 14 | .. automodule:: dwave.gate.registers.registers 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | pydata-sphinx-theme==0.14.3 2 | sphinx==7.3.7 3 | sphinx_autodoc_typehints==1.19.5 4 | 5 | reno[sphinx]==3.3.0 -------------------------------------------------------------------------------- /docs/simulator.rst: -------------------------------------------------------------------------------- 1 | .. _gate_simulators: 2 | 3 | Simulators 4 | ========== 5 | 6 | .. automodule:: dwave.gate.simulator 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | 11 | Operation-Generation Module 12 | --------------------------- 13 | 14 | .. automodule:: dwave.gate.simulator.operation_generation 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | 19 | Simulator Module 20 | ---------------- 21 | 22 | .. automodule:: dwave.gate.simulator.simulator 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | -------------------------------------------------------------------------------- /docs/tools.rst: -------------------------------------------------------------------------------- 1 | .. _gate_tools: 2 | 3 | Tools 4 | ===== 5 | 6 | .. automodule:: dwave.gate.tools 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | 11 | Counters Module 12 | --------------- 13 | 14 | .. automodule:: dwave.gate.tools.counters 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | 19 | Unitary Module 20 | -------------- 21 | 22 | .. automodule:: dwave.gate.tools.unitary 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | -------------------------------------------------------------------------------- /dwave/gate/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from dwave.gate.circuit import * 16 | from dwave.gate.mixedproperty import * 17 | from dwave.gate.primitives import * 18 | 19 | __version__ = "0.3.4" 20 | -------------------------------------------------------------------------------- /dwave/gate/mixedproperty.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2023 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """:class:`mixedproperty` decorator. 16 | 17 | Contains a decorator for creating mixed properties, which differ from regular properties by 18 | allowing access to both the class and the instance. 19 | """ 20 | 21 | from __future__ import annotations 22 | 23 | __all__ = [ 24 | "mixedproperty", 25 | ] 26 | 27 | import inspect 28 | from typing import Any, Callable, Optional 29 | 30 | 31 | class mixedproperty: 32 | """Decorator class for creating a property for a class/instance method. 33 | 34 | Note that the ``mixedproperty`` decorator differs from the regular 35 | ``property`` decorator by supporting access to both the class (``cls``) and 36 | the instance (``self``). The latter is only available when calling the 37 | method on an instance of the class. The signature parameters are positional 38 | and assume that the first parameter is the class and the second parameter 39 | (optional) is the instance. 40 | 41 | This property can optionally also accept parameters (either positional or 42 | keyword) when initialized. Allowed parameters are listed under 'Args' below. 43 | 44 | Args: 45 | self_required: Whether ``self`` is required for the property to be able 46 | to return the requested property, i.e., the function will only work 47 | when called on an instance and not on the class. 48 | 49 | Example: 50 | 51 | .. code-block:: python 52 | 53 | class MixedpropertyExample: 54 | _a = "a_class" 55 | _b = "b_class" 56 | 57 | def __init__(self): 58 | self._b = "b_instance" 59 | self._c = "c_instance" 60 | 61 | # can be called on both instance and class and will 62 | # return the same result regardless 63 | @mixedproperty 64 | def a(cls): 65 | return cls._a 66 | 67 | # can be called on both instance and class and will 68 | # return different results (e.g., different defaults) 69 | @mixedproperty 70 | def b(cls, self): 71 | if self: 72 | return self._b 73 | return cls._b 74 | 75 | # can be called on both instance and class and will 76 | # return 'None' if called on class 77 | @mixedproperty(self_required=True) 78 | def c(cls, self): 79 | return self._c 80 | """ 81 | 82 | def __init__(self, *args, **kwargs) -> None: 83 | self._self_required: bool = kwargs.pop("self_required", False) 84 | if args and isinstance(args[0], Callable): 85 | self.__call__(args[0]) 86 | 87 | def __call__(self, callable_: Callable) -> mixedproperty: 88 | """Initialization of the decorated function. 89 | 90 | Args: 91 | callable_: Method decorated by the ``mixedproperty`` decorator.""" 92 | 93 | # don't patch '__qualname__' due to Sphinx calling the mixedproperty 94 | # at docsbuild, raising exceptions and thus not rendering all entries 95 | for attr in ("__module__", "__name__", "__doc__", "__annotations__"): 96 | self.attr = getattr(callable_, attr) 97 | getattr(self, "__dict__").update(getattr(callable_, "__dict__", {})) 98 | 99 | self.__wrapped__ = callable_ 100 | self._callable = callable_ 101 | return self 102 | 103 | def __get__(self, instance: Optional[object], cls: type) -> Any: 104 | """Return the internal function call. 105 | 106 | Depending on whether the internal function signature contains 2 107 | parameters (class and instance object; normally `cls` and `self`) or 108 | only 1 parameter (only class; normally `cls`), different function calls 109 | are returned. 110 | 111 | Args: 112 | instance: Instance object if called on such. 113 | cls: Class or class of instance. 114 | 115 | Returns: 116 | Any: Output of the decorated method, if any. 117 | """ 118 | num_parameters = len(inspect.signature(self._callable).parameters) 119 | 120 | # if called on class while requiring access to 'self', return 'None' 121 | if self._self_required and num_parameters >= 2 and instance is None: 122 | return None 123 | 124 | if num_parameters == 1: 125 | return self._callable(cls) 126 | 127 | return self._callable(cls, instance) 128 | 129 | def __set__(self, instance: Optional[object], value: Any) -> None: 130 | raise AttributeError("can't set attribute") 131 | -------------------------------------------------------------------------------- /dwave/gate/operations/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2023 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Quantum operations and base operations for creating new operation classes.""" 16 | 17 | from dwave.gate.operations.base import * 18 | from dwave.gate.operations.operations import * 19 | -------------------------------------------------------------------------------- /dwave/gate/primitives.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2023 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Primitive types used with quantum circuits. 16 | 17 | Contains primitive types such as qubits, bits and variables. 18 | """ 19 | 20 | __all__ = [ 21 | "Qubit", 22 | "Bit", 23 | "Variable", 24 | ] 25 | 26 | from typing import Hashable, Optional 27 | 28 | from dwave.gate.tools.counters import IDCounter 29 | 30 | 31 | class Qubit: 32 | """Qubit type. 33 | 34 | Args: 35 | label: Label used to represent the qubit. 36 | """ 37 | 38 | def __init__(self, label: Hashable) -> None: 39 | self._label = label 40 | self._id: str = IDCounter.next() 41 | 42 | @property 43 | def label(self) -> Hashable: 44 | """The qubit's name.""" 45 | return self._label 46 | 47 | @label.setter 48 | def label(self, label: Hashable) -> None: 49 | """Setter method for label""" 50 | self._label = label 51 | 52 | @property 53 | def id(self) -> str: 54 | """The qubit's unique identification number.""" 55 | return self._id 56 | 57 | def __eq__(self, object: object) -> bool: 58 | """Two qubits are equal if they share the same id.""" 59 | if isinstance(object, Qubit): 60 | return self.id == object.id 61 | return False 62 | 63 | def __repr__(self) -> str: 64 | """The representation of the variable is its label.""" 65 | return f"" 66 | 67 | def __hash__(self) -> int: 68 | """The hash of the qubit is determined by its id.""" 69 | return hash(self.__class__.__name__ + self.id) 70 | 71 | 72 | class Bit: 73 | """Classical bit type. 74 | 75 | Args: 76 | label: Label used to represent the bit. 77 | """ 78 | 79 | def __init__(self, label: Hashable) -> None: 80 | self._label = label 81 | self._id: str = IDCounter.next() 82 | self._value: Optional[int] = None 83 | 84 | @property 85 | def label(self) -> Hashable: 86 | """The bit's label.""" 87 | return self._label 88 | 89 | @label.setter 90 | def label(self, label: Hashable) -> None: 91 | """Setter method for label""" 92 | self._label = label 93 | 94 | @property 95 | def id(self) -> str: 96 | """The bit's unique identification number.""" 97 | return self._id 98 | 99 | @property 100 | def value(self) -> Optional[int]: 101 | """The bit value, if set.""" 102 | return self._value 103 | 104 | def __eq__(self, object: object) -> bool: 105 | """Two bits are equal if they share the same id.""" 106 | if isinstance(object, Bit): 107 | return self.id == object.id 108 | if self._value is not None and self._value == object: 109 | return True 110 | return False 111 | 112 | def __repr__(self) -> str: 113 | """The representation of the variable is its label.""" 114 | if self._value is not None: 115 | return f"" 116 | return f"" 117 | 118 | def __hash__(self) -> int: 119 | """The hash of the qubit is determined by its id.""" 120 | return hash(self.__class__.__name__ + self.id) 121 | 122 | def set(self, value: int, force: bool = False) -> None: 123 | """Set a value for the bit. 124 | 125 | Args: 126 | value: Value that the variable should have. 127 | force: Whether to replace any previously set value. 128 | """ 129 | # leniently allow any type that evaluates to 0 or 1 130 | value = int(bool(value)) 131 | 132 | if self._value is not None and not force: 133 | raise ValueError("Value already set. Use 'force=True' to replace it.") 134 | 135 | self._value = value 136 | 137 | def reset(self) -> None: 138 | """Reset the bit value to ``None``.""" 139 | self._value = None 140 | 141 | def __bool__(self) -> bool: 142 | """Bool representation of a bit""" 143 | if self.value is not None: 144 | return bool(self.value) 145 | return True 146 | 147 | 148 | class Variable: 149 | """Variable parameter type. 150 | 151 | Used as a placeholder for parameter values. Two variables with the same label are considered 152 | equal (``Variable('a') == Variable('a')``) and have the same hash value, but not identical 153 | (``Variable('a') is not Variable('a')``) 154 | 155 | Args: 156 | name: String used to represent the variable. 157 | """ 158 | 159 | def __init__(self, name: str) -> None: 160 | self._name = str(name) 161 | 162 | self._value: Optional[complex] = None 163 | 164 | @property 165 | def name(self) -> str: 166 | """The variable name.""" 167 | return self._name 168 | 169 | @property 170 | def value(self) -> Optional[complex]: 171 | """The variable value, if set.""" 172 | return self._value 173 | 174 | def __eq__(self, object: object) -> bool: 175 | """Two variables are equal if they share the same label.""" 176 | if isinstance(object, Variable): 177 | return self.name == object.name 178 | if self._value and self._value == object: 179 | return True 180 | return False 181 | 182 | def __repr__(self) -> str: 183 | """The representation of the variable is its label.""" 184 | if self._value: 185 | return str(self.value) 186 | return f"{{{self.name}}}" 187 | 188 | def __hash__(self) -> int: 189 | """The hash of the variable is determined by its label.""" 190 | return hash(self.name) 191 | 192 | def set(self, value: complex): 193 | """Set a value for the variable. 194 | 195 | Args: 196 | value: Value that the variable should have. 197 | """ 198 | self._value = value 199 | 200 | def reset(self): 201 | """Reset the variable value to ``None``.""" 202 | self._value = None 203 | -------------------------------------------------------------------------------- /dwave/gate/qir/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Quantum Intermediate Representation (QIR) module. 16 | 17 | Contains the QIR compiler, loader and related files and functions. 18 | """ 19 | 20 | try: 21 | import pyqir # noqa: F401 22 | except ImportError as e: # pragma: no cover 23 | raise ImportError("PyQIR required for using the QIR compiler") from e 24 | 25 | 26 | from dwave.gate.qir.compiler import * 27 | from dwave.gate.qir.loader import * 28 | -------------------------------------------------------------------------------- /dwave/gate/qir/instructions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """QIR instructions, types and Operation transformations. 16 | 17 | Contains the Instruction class and the dwave-gate to/from QIR operation transformations. 18 | """ 19 | 20 | __all__ = [ 21 | "InstrType", 22 | "Operations", 23 | "Instruction", 24 | ] 25 | 26 | from dataclasses import dataclass 27 | from enum import Enum, auto 28 | from typing import Any, NamedTuple, Optional, Sequence, Union 29 | 30 | import pyqir 31 | 32 | 33 | class InstrType(Enum): 34 | BASE = auto() 35 | """Valid QIR base profile instructions""" 36 | 37 | EXTERNAL = auto() 38 | """Externally defined instructions. Falls back to decompositions if external 39 | functions are disallowed.""" 40 | 41 | DECOMPOSE = auto() 42 | """Decomposes operation into valid QIR instructions (or external if allowed)""" 43 | 44 | INVALID = auto() 45 | """Invalid instructions that should raise an error if used""" 46 | 47 | SKIP = auto() 48 | """Instructions which are ignored when compiling to QIR (e.g., identity operation)""" 49 | 50 | MEASUREMENT = auto() 51 | """Measurement instructions""" 52 | 53 | OUTPUT = auto() 54 | """QIR output instructions""" 55 | 56 | 57 | @dataclass 58 | class Operations: 59 | """Dataclass containing operation transformations between QIR and ``dwave-gate``.""" 60 | 61 | Op = NamedTuple("Op", [("type", InstrType), ("name", str)]) 62 | 63 | to_qir = { 64 | "Identity": Op(InstrType.SKIP, "id"), 65 | "X": Op(InstrType.BASE, "x"), 66 | "Y": Op(InstrType.BASE, "y"), 67 | "Z": Op(InstrType.BASE, "z"), 68 | "Hadamard": Op(InstrType.BASE, "h"), 69 | "S": Op(InstrType.BASE, "s"), 70 | "T": Op(InstrType.BASE, "t"), 71 | "RX": Op(InstrType.BASE, "rx"), 72 | "RY": Op(InstrType.BASE, "ry"), 73 | "RZ": Op(InstrType.BASE, "rz"), 74 | "Rotation": Op(InstrType.EXTERNAL, "rot"), 75 | "CX": Op(InstrType.BASE, "cx"), 76 | "CY": Op(InstrType.EXTERNAL, "cy"), 77 | "CZ": Op(InstrType.BASE, "cz"), 78 | "SWAP": Op(InstrType.EXTERNAL, "swap"), 79 | "CHadamard": Op(InstrType.EXTERNAL, "ch"), 80 | "CRX": Op(InstrType.EXTERNAL, "crx"), 81 | "CRY": Op(InstrType.EXTERNAL, "cry"), 82 | "CRZ": Op(InstrType.EXTERNAL, "crz"), 83 | "CRotation": Op(InstrType.EXTERNAL, "crot"), 84 | "CSWAP": Op(InstrType.EXTERNAL, "cswap"), 85 | "CCX": Op(InstrType.EXTERNAL, "ccnot"), 86 | "Measurement": Op(InstrType.MEASUREMENT, "mz"), 87 | } 88 | 89 | from_qir = {v.name: k for k, v in to_qir.items()} 90 | 91 | from_qis_operation = { 92 | f"__quantum__qis__{op.name}__body": name 93 | for name, op in to_qir.items() 94 | if op.type not in (InstrType.DECOMPOSE, InstrType.INVALID) 95 | } 96 | 97 | # add special cases 98 | from_qis_operation.update( 99 | { 100 | "__quantum__qis__cnot__body": "CX", # QIR uses CNOT instead of cx (PyQIR) 101 | } 102 | ) 103 | 104 | 105 | class Instruction: 106 | """Container class for QIR instructions. 107 | 108 | Args: 109 | type_: Instruction type. 110 | name: Name of the instruction to be applied. 111 | args: QIR arguments to instruction. 112 | external: Whether the operation is an external operation. 113 | """ 114 | 115 | def __init__( 116 | self, 117 | type_: InstrType, 118 | name: str, 119 | args: Sequence[Union[int, float, pyqir.Constant]], 120 | external: Optional[pyqir.Function] = None, 121 | ) -> None: 122 | self._type = type_ 123 | self._name = name 124 | 125 | self._assert_args(args) 126 | self._args = args 127 | 128 | if self.type is InstrType.EXTERNAL and not external: 129 | raise ValueError("Instruction with type 'external' missing external function.") 130 | 131 | self._external = external 132 | 133 | def _assert_args(self, args: Sequence[Any]) -> None: 134 | """Asserts that ``args`` only contain valid arguments. 135 | 136 | Args: 137 | args: Sequence of arguments to check. 138 | """ 139 | for arg in args: 140 | if not isinstance(arg, (int, float, pyqir.Constant)): 141 | raise TypeError(f"Incorrect type {type(arg)} in arguments.") 142 | 143 | @property 144 | def name(self) -> str: 145 | """Instruction to apply.""" 146 | return self._name 147 | 148 | @property 149 | def type(self) -> InstrType: 150 | """The instruction type.""" 151 | return self._type 152 | 153 | @property 154 | def args(self) -> Sequence[Any]: 155 | """Optional arguments to instruction.""" 156 | return self._args 157 | 158 | def execute(self, builder: pyqir.Builder) -> None: 159 | """Executes operation using the provided builder. 160 | 161 | Args: 162 | builder: PyQIR module builder. 163 | """ 164 | if self._type in (InstrType.BASE, InstrType.MEASUREMENT): 165 | self._execute_qis(builder) 166 | elif self._type is InstrType.EXTERNAL: 167 | self._execute_external(builder) 168 | else: 169 | raise TypeError(f"Cannot execute instruction of type '{self._type.name}'") 170 | 171 | def _execute_qis(self, builder: pyqir.Builder) -> None: 172 | """Executes QIS operation using the provided builder. 173 | 174 | Args: 175 | builder: PyQIR module builder. 176 | """ 177 | getattr(pyqir.qis, self.name)(builder, *self.args) 178 | 179 | def _execute_external(self, builder: pyqir.Builder) -> None: 180 | """Executes external operation using the provided builder. 181 | 182 | Args: 183 | builder: PyQIR module builder. 184 | """ 185 | builder.call(self._external, self.args) 186 | 187 | def __eq__(self, value: object) -> bool: 188 | """Whether two instructions are considered equal. 189 | 190 | Two instructions are considered equal if they have the same name, type, 191 | number of qubits and number of results. 192 | """ 193 | eq_name = self.name == value.name 194 | eq_type = self.type == value.type 195 | 196 | num_qubits_self = [ 197 | a.type.pointee.name == "Qubit" for a in self.args if isinstance(a, pyqir.Constant) 198 | ].count(True) 199 | 200 | num_qubits_value = [ 201 | a.type.pointee.name == "Qubit" for a in value.args if isinstance(a, pyqir.Constant) 202 | ].count(True) 203 | 204 | num_results_self = [ 205 | a.type.pointee.name == "Result" for a in self.args if isinstance(a, pyqir.Constant) 206 | ].count(True) 207 | 208 | num_results_value = [ 209 | a.type.pointee.name == "Result" for a in value.args if isinstance(a, pyqir.Constant) 210 | ].count(True) 211 | 212 | parameters_self = [a for a in self.args if isinstance(a, (int, float))] 213 | parameters_value = [a for a in value.args if isinstance(a, (int, float))] 214 | 215 | eq_qubits = num_qubits_self == num_qubits_value 216 | eq_results = num_results_self == num_results_value 217 | eq_parameters = parameters_self == parameters_value 218 | 219 | if eq_name and eq_type and eq_qubits and eq_results and eq_parameters: 220 | return True 221 | return False 222 | -------------------------------------------------------------------------------- /dwave/gate/qir/loader.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """QIR circuit loader. 16 | 17 | Contains the QIR-to-dwave-gate circuit object loaders. 18 | """ 19 | 20 | __all__ = [ 21 | "load_qir_bitcode", 22 | "load_qir_string", 23 | ] 24 | 25 | import re 26 | from collections.abc import Sequence 27 | from typing import Optional 28 | 29 | import pyqir 30 | from pyqir import Context, Module, Opcode 31 | 32 | import dwave.gate.operations as ops 33 | from dwave.gate import Circuit 34 | from dwave.gate.qir.instructions import Operations 35 | 36 | 37 | def load_qir_bitcode( 38 | qir_bitcode: bytes, circuit: Optional[Circuit] = None, context: Optional[Context] = None 39 | ) -> Circuit: 40 | """Loads QIR bitcode into a ``Circuit`` object. 41 | 42 | Args: 43 | qir_bitcode: QIR bitcode representation of the circuit. 44 | circuit: Optional circuit into which to load QIR bitcode. If ``None`` 45 | a new circuit is created (default). 46 | context: Context to use to construct the module. 47 | 48 | Returns: 49 | Circuit: Circuit representation of the QIR. 50 | """ 51 | module = Module.from_bitcode(context or Context(), qir_bitcode) 52 | return _module_to_circuit(module, circuit) 53 | 54 | 55 | def load_qir_string( 56 | qir_str: str, circuit: Optional[Circuit] = None, context: Context = None 57 | ) -> Circuit: 58 | """Loads a QIR string into a ``Circuit`` object. 59 | 60 | Args: 61 | qir_str: QIR string representation of the circuit. 62 | circuit: Optional circuit into which to load QIR bitcode. If ``None`` 63 | a new circuit is created (default). 64 | context: Context to use to construct the module. 65 | 66 | Returns: 67 | Circuit: Circuit representation of the QIR. 68 | """ 69 | module = Module.from_ir(context or Context(), qir_str) 70 | return _module_to_circuit(module, circuit) 71 | 72 | 73 | def _module_to_circuit(module: Module, circuit: Optional[Circuit] = None) -> Circuit: 74 | """Parses a PyQIR module into a ``Circuit`` object. 75 | 76 | Args: 77 | module: PyQIR module containing the QIR representation of the circuit. 78 | circuit: Optional circuit into which to load QIR bitcode. If ``None`` 79 | a new circuit is created (default). 80 | 81 | Returns: 82 | Circuit: Circuit representation of the QIR. 83 | """ 84 | if circuit is None: 85 | circuit = Circuit() 86 | 87 | qubits = [] 88 | 89 | meas_qubits = [] 90 | meas_bits = [] 91 | 92 | for func in module.functions: 93 | ret_set = False 94 | for block in func.basic_blocks: 95 | # if return has been set, then ignore rest of blocks 96 | if ret_set: 97 | break 98 | 99 | for instr in block.instructions: 100 | if instr.opcode == Opcode.RET: 101 | # break block on return code 102 | ret_set = True 103 | break 104 | 105 | if instr.opcode == Opcode.CALL: 106 | # construct and add operations to circuit 107 | op_name = Operations.from_qis_operation.get(instr.callee.name, None) 108 | if "__qis__" not in instr.callee.name: 109 | # only add non-ignored QIS operations 110 | continue 111 | elif op_name is None: 112 | raise TypeError(f"'{instr.callee.name}' not found in valid QIS operations.") 113 | 114 | params, qubits, bits = _deconstruct_call_instruction(instr) 115 | 116 | if op_name == "Measurement": 117 | meas_qubits.extend(qubits) 118 | meas_bits.extend(bits) 119 | # delay adding until all measurements are collected 120 | continue 121 | 122 | for _ in range(circuit.num_qubits, max(qubits or [0]) + 1): 123 | circuit.add_qubit() 124 | 125 | circuit.unlock() 126 | with circuit.context as reg: 127 | getattr(ops, op_name)(*params, qubits=[reg.q[qubit] for qubit in qubits]) 128 | 129 | # finally, add measurements (if any) to circuit 130 | if meas_qubits: 131 | _add_measurements(circuit, meas_qubits, meas_bits) 132 | 133 | return circuit 134 | 135 | 136 | def _add_measurements(circuit: Circuit, qubits: Sequence[int], bits: Sequence[int]) -> None: 137 | """Add measurements to circuit. 138 | 139 | Args: 140 | circuit: The circuit to add the measurments to. 141 | qubits: The qubits that are being measured. 142 | bits: The bits in which the measurement values are stored. 143 | """ 144 | for _ in range(circuit.num_bits, max(bits or [0]) + 1): 145 | circuit.add_bit() 146 | 147 | circuit.unlock() 148 | with circuit.context as reg: 149 | qubits = [reg.q[qubit] for qubit in qubits] 150 | bits = [reg.c[bit] for bit in bits] 151 | 152 | ops.Measurement(qubits=qubits) | bits 153 | 154 | 155 | def _deconstruct_call_instruction( 156 | instr: pyqir.Instruction, 157 | ) -> tuple[Sequence[float], Sequence[int]]: 158 | """Extracts parameters and qubits from a call instruction. 159 | 160 | Args: 161 | instr: PyQIR instruction to deconstruct. 162 | 163 | Returns: 164 | tuple: Containing the parameters and qubits of the instruction/operation. 165 | """ 166 | params = [] 167 | qubits = [] 168 | bits = [] 169 | for arg in instr.args: 170 | # only supports doubles 171 | if arg.type.is_double: 172 | params.append(arg.value) 173 | elif arg.type.pointee.name == "Qubit": 174 | if arg.is_null: 175 | qubits.append(0) 176 | else: 177 | pattern = "\%Qubit\* inttoptr \(i64 (\d+) to \%Qubit\*\)" 178 | qubit = re.search(pattern, str(arg)).groups()[0] 179 | qubits.append(int(qubit) if qubit.isdigit() else None) 180 | else: 181 | assert arg.type.pointee.name == "Result" 182 | if arg.is_null: 183 | bits.append(0) 184 | else: 185 | pattern = "\%Result\* inttoptr \(i64 (\d+) to \%Result\*\)" 186 | bit = re.search(pattern, arg.__str__()).groups()[0] 187 | bits.append(int(bit) if bit.isdigit() else None) 188 | 189 | return params, qubits, bits 190 | -------------------------------------------------------------------------------- /dwave/gate/registers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023-2023 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Registers for collecting qubits, bits and variables. 16 | 17 | Provides performant container classes for keeping track of and handling primitive type objects (see 18 | :py:mod:`dwave.gate.primitives` for details on the supported objects). 19 | """ 20 | 21 | from dwave.gate.registers.registers import * 22 | -------------------------------------------------------------------------------- /dwave/gate/registers/cyregister.pxd: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | # cython: language_level=3 3 | 4 | # Copyright 2022 D-Wave Systems Inc. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | __all__ = ['cyRegister'] 19 | 20 | cdef class cyRegister: 21 | cdef object _index_to_label 22 | cdef object _label_to_index 23 | cdef Py_ssize_t _stop 24 | 25 | cdef object at(self, Py_ssize_t) 26 | cdef Py_ssize_t size(self) 27 | 28 | cpdef object _append(self, object v=*, bint permissive=*) 29 | cpdef void _clear(self) 30 | cpdef object _extend(self, object iterable, bint permissive=*) 31 | cpdef bint _is_range(self) 32 | cpdef object _pop(self) 33 | cpdef cyRegister copy(self) 34 | cdef Py_ssize_t _count_int(self, object) except -1 35 | cpdef Py_ssize_t count(self, object) except -1 36 | cpdef Py_ssize_t index(self, object, bint permissive=*) except -1 37 | cpdef _remove(self, object) 38 | -------------------------------------------------------------------------------- /dwave/gate/registers/cyregister.pyi: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # as of Cython 0.29.28, __annotations__ are not included for Cython objects. 16 | # so we specify their types here. 17 | 18 | from typing import Hashable, Iterable, Iterator, Mapping, Optional, TypeVar, overload 19 | 20 | T = TypeVar("T") 21 | 22 | class cyRegister: 23 | def __init__(self, iterable: Optional[Iterable[Hashable]] = None): ... 24 | def __contains__(self, v: Hashable) -> bool: ... 25 | def __copy__(self: T) -> T: ... 26 | @overload 27 | def __getitem__(self, idx: int) -> Hashable: ... 28 | @overload 29 | def __getitem__(self: T, idx: slice) -> T: ... 30 | def __iter__(self) -> Iterator[Hashable]: ... 31 | def __len__(self) -> int: ... 32 | def _append(self, v: Optional[Hashable] = None, permissive: bool = False) -> Hashable: ... 33 | def _clear(self) -> None: ... 34 | def _is_range(self) -> bool: ... 35 | def _extend(self, iterable: Iterable[Hashable], permissive: bool = False) -> None: ... 36 | def _pop(self) -> Hashable: ... 37 | def _relabel(self, mapping: Mapping[Hashable, Hashable]) -> None: ... 38 | def _relabel_as_integers(self) -> Mapping[int, Hashable]: ... 39 | def _remove(self, v: Hashable) -> None: ... 40 | def copy(self: T) -> T: ... 41 | def count(self, v: Hashable) -> int: ... 42 | def index(self, v: Hashable, permissive: bool = False) -> int: ... 43 | -------------------------------------------------------------------------------- /dwave/gate/registers/registers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from __future__ import annotations 16 | 17 | __all__ = [ 18 | "RegisterError", 19 | "Register", 20 | "QuantumRegister", 21 | "ClassicalRegister", 22 | "SelfIncrementingRegister", 23 | ] 24 | 25 | from collections.abc import Hashable, Iterator, Sequence 26 | from typing import TYPE_CHECKING, AbstractSet, Optional, TypeVar, Union 27 | 28 | from dwave.gate.primitives import Bit, Qubit, Variable 29 | from dwave.gate.registers.cyregister import cyRegister 30 | 31 | if TYPE_CHECKING: 32 | from typing_extensions import Self 33 | 34 | Data = TypeVar("Data", bound=Hashable) 35 | 36 | 37 | class RegisterError(Exception): 38 | """Exception to be raised when there is an error with a Register.""" 39 | 40 | 41 | class Register(cyRegister, AbstractSet[Data], Sequence[Data]): 42 | """Register to store qubits and/or classical bits. 43 | 44 | Args: 45 | data: Sequence of hashable data items (defaults to empty). 46 | """ 47 | 48 | def __init__(self, data: Optional[Sequence[Data]] = None) -> None: 49 | self._frozen = False 50 | super().__init__(data or []) 51 | 52 | def __getitem__(self, idx) -> Data: 53 | """Get item at index.""" 54 | return super().__getitem__(idx) # type: ignore 55 | 56 | def __iter__(self) -> Iterator[Data]: 57 | """Return a data iterator.""" 58 | return super().__iter__() # type: ignore 59 | 60 | @property 61 | def data(self) -> Sequence[Data]: 62 | """Sequence of qubits or bits.""" 63 | return list(self) 64 | 65 | @property 66 | def frozen(self) -> bool: 67 | """Whether the register is frozen and no more data can be added.""" 68 | return self._frozen 69 | 70 | def freeze(self) -> None: 71 | """Freezes the register so that no (qu)bits can be added or removed.""" 72 | self._frozen = True 73 | 74 | def __str__(self) -> str: 75 | """Returns the data as a string.""" 76 | return f"{self.__class__.__name__}({list(self)})" 77 | 78 | def __repr__(self) -> str: 79 | """Returns the representation of the Register object.""" 80 | return f"<{self.__class__.__name__}, data={list(self)}>" 81 | 82 | def __add__(self, qreg: Register) -> Self: 83 | """Adds two registers together and returns a register referencing the contained items. 84 | 85 | Args: 86 | qreg: Register to add to ``self``. 87 | 88 | Returns: 89 | Register: Combined register referencing the contained items. 90 | """ 91 | copy = self.copy() 92 | copy._extend(qreg) 93 | return copy 94 | 95 | def add(self, data: Union[Hashable, Sequence[Hashable], Register]) -> None: 96 | """Add one or more data items to the register. 97 | 98 | Args: 99 | data: One or more data items to add to the register. 100 | """ 101 | if self.frozen: 102 | raise RegisterError("Register is frozen and no more data can be added.") 103 | 104 | if not isinstance(data, Register) and ( 105 | isinstance(data, str) or not isinstance(data, Sequence) 106 | ): 107 | data = [data] 108 | 109 | self._extend(data) 110 | 111 | 112 | class QuantumRegister(Register[Qubit]): 113 | """Quantum register to store qubits. 114 | 115 | Args: 116 | data: Sequence of qubits (defaults to empty). 117 | """ 118 | 119 | def __init__(self, data: Optional[Sequence[Qubit]] = None) -> None: 120 | super().__init__(data) 121 | 122 | def to_qasm(self, label: Hashable = None, idx: Optional[int] = None) -> str: 123 | """Converts the quantum register into an OpenQASM string. 124 | 125 | Args: 126 | label: Optional label for the quantum register. 127 | idx: Optional index number for quantum register. 128 | 129 | Returns: 130 | str: OpenQASM string representation of the circuit. 131 | """ 132 | if label: 133 | return f"qreg {str(label)}[{len(self)}]" 134 | 135 | idx_str = str(idx) if idx is not None else "" 136 | return f"qreg q{idx_str}[{len(self)}]" 137 | 138 | def freeze(self) -> None: 139 | """Freezes the register so that no qubits can be added or removed.""" 140 | return super().freeze() 141 | 142 | 143 | class ClassicalRegister(Register[Bit]): 144 | """Classical register to store bits. 145 | 146 | Args: 147 | data: Sequence of bits (defaults to empty). 148 | """ 149 | 150 | def __init__(self, data: Optional[Sequence[Bit]] = None) -> None: 151 | super().__init__(data) 152 | 153 | def to_qasm(self, label: Hashable = None, idx: Optional[int] = None) -> str: 154 | """Converts the classical register into an OpenQASM string. 155 | 156 | Args: 157 | label: Optional label for the quantum register. 158 | idx: Optional index number for quantum register. 159 | 160 | Returns: 161 | str: OpenQASM string representation of the circuit. 162 | """ 163 | if label: 164 | return f"creg {str(label)}[{len(self)}]" 165 | 166 | idx_str = str(idx) if idx is not None else "" 167 | return f"creg c{idx_str}[{len(self)}]" 168 | 169 | def freeze(self) -> None: 170 | """Freezes the register so that no bits can be added or removed.""" 171 | return super().freeze() 172 | 173 | 174 | class SelfIncrementingRegister(Register): 175 | """Self-incrementing classical register to store parameter variables. 176 | 177 | The self-incrementing register will automatically add variable parameters when attemting to 178 | index outside of the register scope, and then return the requested index. For example, 179 | attempting to get parameter at index 3 in a ``SelfIncrementingRegister`` of length 1 would be 180 | equal to first running ``Register.add([Variable("p1"), Variable("p2")])`` and then returning 181 | ``Variable("p2")``. 182 | 183 | Args: 184 | label: Classical register label. data: Sequence of bits (defaults to empty). 185 | data: Sequence of parameters or variables (defaults to empty). 186 | """ 187 | 188 | def __init__(self, data: Optional[Sequence[Variable]] = None) -> None: 189 | super().__init__(data) 190 | 191 | def freeze(self) -> None: 192 | """Freezes the register so that no data can be added or removed.""" 193 | return super().freeze() 194 | 195 | def __getitem__(self, index: int) -> Variable: 196 | """Return the parameter at a specified index. 197 | 198 | Warning, will _not_ raise an ``IndexError`` if attempting to access outside of the registers 199 | data sequence. It will instead automatically add variable parameters when attemting to index 200 | outside of the scope, and then return the newly created variable at the requested index. 201 | 202 | Args: 203 | Index of the parameter to be returned. 204 | 205 | Returns: 206 | Hashable: The parameter at the specified index. 207 | """ 208 | if index >= len(self.data) and not self.frozen: 209 | self.add([Variable(str(i)) for i in range(len(self.data), index + 1)]) 210 | return super().__getitem__(index) # type: ignore 211 | -------------------------------------------------------------------------------- /dwave/gate/simulator/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2023 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Simulators for running circuits locally. 16 | 17 | Contains a state-vector simulator able to return the resulting state vector after running a circuit 18 | on an all-zero initialized basis state. Supports both the little- and big-endian conventions. 19 | """ 20 | 21 | from dwave.gate.simulator.simulator import * # type: ignore 22 | -------------------------------------------------------------------------------- /dwave/gate/simulator/simulator.pyx: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | # cython: language_level=3 3 | # cython: linetrace=True 4 | 5 | # Copyright 2022 D-Wave Systems Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | __all__ = [ 20 | "simulate", 21 | "sample_qubit", 22 | ] 23 | 24 | import random 25 | import warnings 26 | from typing import List, Optional 27 | 28 | import numpy as np 29 | cimport numpy as np 30 | 31 | import dwave.gate.operations as ops 32 | from dwave.gate.circuit import Circuit, CircuitError 33 | 34 | from dwave.gate.simulator.ops cimport ( 35 | apply_cswap, 36 | apply_gate_control, 37 | apply_gate_two_control, 38 | apply_swap, 39 | measurement_computational_basis, 40 | single_qubit, 41 | ) 42 | 43 | 44 | def apply_instruction( 45 | num_qubits: int, 46 | state: np.ndarray, 47 | op: ops.Operation, 48 | targets: list[int], 49 | little_endian: bool, 50 | rng: np.random.Generator, 51 | conjugate_gate: bool = False, 52 | ): 53 | if op.is_blocked: 54 | return 55 | 56 | if isinstance(op, ops.Measurement): 57 | if little_endian: 58 | raise CircuitError("Measurements only supports big-endian qubit indexing.") 59 | _measure(op, state, targets, rng) 60 | 61 | elif isinstance(op, ops.SWAP): 62 | gate = op.matrix 63 | target0 = targets[1] 64 | target1 = targets[0] 65 | apply_swap( 66 | num_qubits, state, gate, target0, target1, 67 | little_endian=little_endian, 68 | ) 69 | 70 | elif isinstance(op, ops.CSWAP): 71 | gate = op.matrix 72 | target0 = targets[1] 73 | target1 = targets[2] 74 | control = targets[0] 75 | apply_cswap( 76 | num_qubits, state, gate, target0, target1, control, 77 | little_endian=little_endian, 78 | ) 79 | 80 | elif isinstance(op, ops.CCX): 81 | # this one has to hardcoded for now because ops.ControlledOperation 82 | # doesn't support more than one control yet 83 | gate = np.array([[0, 1], [1, 0]], dtype=np.complex128) 84 | target = targets[2] 85 | control0 = targets[0] 86 | control1 = targets[1] 87 | apply_gate_two_control( 88 | num_qubits, state, gate, target, control0, control1, 89 | little_endian=little_endian, 90 | ) 91 | 92 | elif isinstance(op, ops.ControlledOperation): 93 | # should be single qubit gate with controls 94 | if isinstance(op, ops.ParametricOperation): 95 | gate = op.target_operation(op.parameters).matrix 96 | else: 97 | gate = op.target_operation.matrix 98 | assert gate.shape == (2, 2), (op, gate) 99 | if conjugate_gate: 100 | gate = np.ascontiguousarray(gate.conjugate()) 101 | 102 | if op.num_control == 1: 103 | target = targets[1] 104 | control = targets[0] 105 | apply_gate_control( 106 | num_qubits, state, gate, target, control, 107 | little_endian=little_endian, 108 | ) 109 | 110 | elif op.num_control == 2: 111 | target = targets[2] 112 | control0 = targets[0] 113 | control1 = targets[1] 114 | 115 | apply_gate_two_control( 116 | num_qubits, state, gate, target, control0, control1, 117 | little_endian=little_endian, 118 | ) 119 | else: 120 | # apply single qubit gate 121 | if op.num_qubits != 1: 122 | raise ValueError( 123 | f"simulator encountered unknown multi-qubit operation: {op.label}" 124 | ) 125 | gate = op.matrix 126 | if conjugate_gate: 127 | gate = np.ascontiguousarray(gate.conjugate()) 128 | 129 | target = targets[0] 130 | single_qubit(num_qubits, state, gate, target, little_endian=little_endian) 131 | 132 | 133 | def simulate( 134 | circuit: Circuit, 135 | mixed_state: bool = False, 136 | little_endian: bool = False, 137 | rng_seed: Optional[int] = None, 138 | ) -> None: 139 | """Simulate the given circuit with either a state vector or density matrix simulation. 140 | 141 | The resulting state is stored in the circuit object, together with the measured value in the 142 | classical register. 143 | 144 | Args: 145 | circuit: The circuit to simulate. 146 | 147 | mixed_state: 148 | If true, use the full density matrix method to simulate the circuit. 149 | Otherwise, simulate using the state vector method. 150 | 151 | little_endian: 152 | If true, return the state vector using little-endian indexing for 153 | the qubits. Otherwise use big-endian. 154 | 155 | """ 156 | 157 | num_qubits = circuit.num_qubits 158 | if num_qubits == 0: 159 | return 160 | 161 | rng = np.random.default_rng(rng_seed) 162 | 163 | if mixed_state: 164 | state = _simulate_circuit_density_matrix(circuit, rng) 165 | else: 166 | state = np.zeros(1 << num_qubits, dtype=np.complex128) 167 | state[0] = 1 168 | 169 | for op in circuit.circuit: 170 | targets = [circuit.qubits.index(qb) for qb in op.qubits] 171 | apply_instruction(num_qubits, state, op, targets, little_endian, rng) 172 | 173 | circuit.set_state(state, force=True) 174 | 175 | 176 | def sample_qubit( 177 | qubit: int, 178 | state: np.typing.NDArray, 179 | rng: np.random.Generator, 180 | collapse_state: bool = True, 181 | little_endian: bool = False, 182 | ) -> int: 183 | """Sample a single qubit. 184 | 185 | Args: 186 | qubit: The qubit index that is measured. 187 | state: The state to sample from. 188 | rng: Random number generator to use for measuring in the computational basis. 189 | collapse_state: Whether to collapse the state after measuring. 190 | little_endian: If true, return the state vector using little-endian indexing for 191 | the qubits. Otherwise use big-endian. 192 | 193 | Returns: 194 | int: The measurement sample (0 or 1). 195 | """ 196 | cdef int num_qubits = round(np.log2(state.shape[0])) 197 | 198 | return measurement_computational_basis( 199 | num_qubits, 200 | state, 201 | qubit, 202 | rng, 203 | little_endian=little_endian, 204 | apply_operator=collapse_state, 205 | ) 206 | 207 | 208 | def _measure(op, state, targets, rng): 209 | op._measured_state = state.copy() 210 | 211 | for idx, t in enumerate(targets): 212 | m = sample_qubit(t, state, rng) 213 | 214 | try: 215 | op.bits[idx].set(m, force=True) 216 | except (IndexError, TypeError): 217 | warnings.warn("Measurements not stored in the classical register.") 218 | 219 | op._measured_qubit_indices.append(t) 220 | 221 | 222 | def _simulate_circuit_density_matrix( 223 | circuit: Circuit, 224 | rng: np.random.Generator, 225 | little_endian: bool = False, 226 | ) -> np.typing.NDArray: 227 | 228 | num_qubits = circuit.num_qubits 229 | num_virtual_qubits = 2 * num_qubits 230 | state = np.zeros(1 << num_virtual_qubits, dtype=np.complex128) 231 | state[0] = 1 232 | 233 | for op in circuit.circuit: 234 | if isinstance(op, ops.Measurement): 235 | _measure(op, state, circuit, rng) 236 | else: 237 | # first apply the gate normally 238 | targets = [circuit.qubits.index(qb) for qb in op.qubits] 239 | apply_instruction( 240 | num_virtual_qubits, state, op, targets, little_endian, rng 241 | ) 242 | # then apply conjugate transpose to corresponding virtual qubit 243 | virtual_targets = [t + num_qubits for t in targets] 244 | apply_instruction( 245 | num_virtual_qubits, state, op, virtual_targets, little_endian, rng, 246 | conjugate_gate=True, 247 | ) 248 | density_matrix = state.reshape((1 << num_qubits, 1 << num_qubits)) 249 | 250 | return density_matrix 251 | -------------------------------------------------------------------------------- /dwave/gate/tools/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2023 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Set of tools that are useful for circuit- and operation-creation. 16 | 17 | Contains a set of modules with helper functions for generation of different unitaries as well 18 | as labels and unique ID's that are used when constructing primitives such as qubits and bits. 19 | """ 20 | from dwave.gate.tools.counters import * 21 | from dwave.gate.tools.unitary import * 22 | -------------------------------------------------------------------------------- /dwave/gate/tools/counters.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | __all__ = [ 16 | "IDCounter", 17 | ] 18 | 19 | import itertools 20 | import warnings 21 | from typing import Optional 22 | 23 | 24 | class IDCounter: 25 | """ID number counter. 26 | 27 | Generates pseudo-random alphanumeric ID numbers with a certain length in batches. If all ID 28 | numbers have been used in a batch, a new batch is generated. If all ID numbers of the declared 29 | length has been used, the length is incremented by one and ID numbers of the new length is 30 | generated instead. 31 | """ 32 | 33 | length = _default_length = 4 34 | batch = _default_batch = 1000 35 | 36 | # alphanumeric string containing digits 0-9 and letters a-z to generate IDs; 37 | # starts with 'q' for quantum, mixes digits and letters to look more random 38 | _alphanum = "q0a1b2c3d4e5f6g7h8i9jklmnoprstuvwxyz" 39 | _id_gen = itertools.combinations(_alphanum, r=length) 40 | 41 | id_set = set() 42 | 43 | @classmethod 44 | def next(cls) -> str: 45 | """Returns a semi-random (unique) alphanumeric ID.""" 46 | if not cls.id_set: 47 | cls.refresh() 48 | return cls.id_set.pop() 49 | 50 | @classmethod 51 | def refresh(cls) -> None: 52 | """Refreshes the set of available ID numbers; automatically done when necessary.""" 53 | for _ in range(cls.batch): 54 | try: 55 | cls.id_set.add("".join(next(cls._id_gen))) 56 | 57 | except StopIteration: 58 | cls.length += 1 59 | if cls.length > len(cls._alphanum): 60 | if not cls.id_set: 61 | raise ValueError( 62 | "ID length cannot be longer than number of unique characters available." 63 | ) 64 | warnings.warn( 65 | f"Insufficient characters to generate unique ID of length {cls.length} or " 66 | f"longer. Generated batch of {len(cls.id_set)} instead of requested " 67 | f"batch size of {cls.batch} IDs" 68 | ) 69 | break 70 | 71 | cls._id_gen = itertools.combinations(cls._alphanum, r=cls.length) 72 | cls.id_set.add("".join(next(cls._id_gen))) 73 | 74 | continue 75 | 76 | @classmethod 77 | def reset(cls, length: Optional[int] = None, batch: Optional[int] = None) -> None: 78 | """Resets the ID counter to use a certain length and/or batch size. 79 | 80 | Args: 81 | length: The (initial) length of unique ID numbers. 82 | batch: The size of each generated batch of ID numbers. The lower the number, the less 83 | variation there will be between IDs; the higher the number, the more variation 84 | there will be, but with a higher memory and performance impact at refresh time. 85 | """ 86 | cls.length = length or cls._default_length 87 | cls.batch = batch or cls._default_batch 88 | 89 | cls._id_gen = itertools.combinations(cls._alphanum, r=cls.length) 90 | 91 | cls.id_set = set() 92 | -------------------------------------------------------------------------------- /dwave/gate/tools/unitary.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from __future__ import annotations 16 | 17 | __all__ = [ 18 | "build_unitary", 19 | "build_controlled_unitary", 20 | ] 21 | 22 | import itertools 23 | from collections.abc import Sequence 24 | from functools import lru_cache 25 | from typing import TYPE_CHECKING, Optional, Union 26 | 27 | import numpy as np 28 | 29 | if TYPE_CHECKING: 30 | from numpy.typing import DTypeLike, NDArray 31 | 32 | from dwave.gate.operations.base import ControlledOperation, Operation 33 | 34 | 35 | ##################### 36 | # Circuit functions # 37 | ##################### 38 | 39 | 40 | # TODO: exchange for something better; only here for testing matrix creation 41 | # for custom operations; controlled operations only works with single 42 | # control and target; no support for any other multi-qubit gates 43 | @lru_cache(maxsize=128) 44 | def build_unitary(circuit) -> NDArray: 45 | """Builds the circuit unitary by multiplying together the operation matrices. 46 | 47 | Args: 48 | circuit: The circuit for which the unitary should be built. 49 | 50 | Returns: 51 | NDArray: Unitary matrix representation of the circuit. 52 | """ 53 | state = np.eye(2**circuit.num_qubits) 54 | # apply operations from first to last (the order in which they're 55 | # applied within the context, stored sequentially in the circuit) 56 | for op in circuit.circuit: 57 | # check if controlled operation; cannot check isinstance 58 | # 'Controlled' due to circular import issue 59 | if hasattr(op, "control"): 60 | state = _apply_controlled_gate(state, op, circuit.qubits) 61 | else: 62 | state = _apply_single_qubit_gate(state, op, circuit.qubits) 63 | return state 64 | 65 | 66 | def _apply_single_qubit_gate(state: NDArray, op: Operation, qubits) -> NDArray: 67 | """Apply a single qubit operation to the state. 68 | 69 | Args: 70 | state: State on which to apply the operation. 71 | op: Single-qubit operation to apply (must have ``op.qubits``). 72 | qubits: List of qubits in the circuit. 73 | 74 | Returns: 75 | NDArray: 76 | """ 77 | if op.qubits and op.qubits[0] == qubits[0]: 78 | mat = op.matrix 79 | else: 80 | mat = np.eye(2**op.num_qubits) 81 | 82 | for qb in qubits[1:]: 83 | if op.qubits and qb == op.qubits[0]: 84 | mat = np.kron(mat, op.matrix) 85 | else: 86 | mat = np.kron(mat, np.eye(2**op.num_qubits)) 87 | 88 | return mat @ state 89 | 90 | 91 | def _apply_controlled_gate(state: NDArray, op: ControlledOperation, qubits) -> NDArray: 92 | """Apply a controlled qubit gate to the state. 93 | 94 | Args: 95 | state: State on which to apply the operation. 96 | op: Controlled operation to apply (must have ``op.control``, 97 | and ``op.target`` defined). 98 | qubits: List of qubits in the circuit. 99 | 100 | Returns: 101 | NDArray: Resulting state vector after application. 102 | """ 103 | control_idx = [qubits.index(c) for c in op.control or []] 104 | target_idx = [qubits.index(t) for t in op.target or []] 105 | controlled_unitary = build_controlled_unitary( 106 | control_idx, target_idx, op.target_operation.matrix, len(qubits) 107 | ) 108 | return controlled_unitary @ state 109 | 110 | 111 | ######################### 112 | # Stand-alone functions # 113 | ######################### 114 | 115 | 116 | def build_controlled_unitary( 117 | control: Union[int, Sequence[int]], 118 | target: Union[int, Sequence[int]], 119 | unitary: NDArray, 120 | num_qubits: Optional[int] = None, 121 | dtype: DTypeLike = complex, 122 | ) -> NDArray: 123 | """Build the unitary matrix for a controlled operation. 124 | 125 | Args: 126 | control: Index of control qubit(s). 127 | target: Index of target qubit. Only a single target supported. 128 | num_qubits: Total number of qubits. 129 | 130 | Returns: 131 | NDArray: Unitary matrix representing the controlled operation. 132 | """ 133 | if isinstance(control, int): 134 | control = [control] 135 | if isinstance(target, int): 136 | target = [target] 137 | 138 | if not set(control).isdisjoint(target): 139 | raise ValueError("Control qubits and target qubit cannot be the same.") 140 | 141 | max_ = max(itertools.chain.from_iterable((control, target))) 142 | if isinstance(num_qubits, int) and num_qubits <= max_: 143 | raise ValueError( 144 | f"Total number of qubits {num_qubits} must be larger or equal " 145 | f"to the largest qubit index {max_}." 146 | ) 147 | 148 | # TODO: add support for multiple targets 149 | if len(target) != 1: 150 | raise NotImplementedError("Multiple target not currently supported.") 151 | 152 | # if None, set number of qubits to the max control/target value + 1 153 | num_qubits = num_qubits or max_ + 1 154 | 155 | state = np.eye(2**num_qubits, dtype=dtype) 156 | 157 | # create all control and target bitstrings (ignore the ones that don't change) 158 | for bitstring in itertools.product( 159 | ["0", "1"], repeat=num_qubits - (len(control) + len(target)) 160 | ): 161 | qubits = sorted([(c, "1") for c in control] + [(t, "0") for t in target]) 162 | 163 | control_bitstring = list(bitstring) 164 | target_bitstring = list(bitstring) 165 | 166 | for idx, type_ in qubits: 167 | control_bitstring.insert(idx, type_) 168 | target_bitstring.insert(idx, "1") 169 | 170 | # find indices for control/target bitstrings and insert unitary 171 | idx_0, idx_1 = int("".join(control_bitstring), 2), int("".join(target_bitstring), 2) 172 | state[idx_0], state[idx_1] = unitary @ [ 173 | state[idx_0], 174 | state[idx_1], 175 | ] 176 | 177 | return state 178 | -------------------------------------------------------------------------------- /examples/0_circuit.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Building, modifying and excecuting a circuit.""" 16 | from dwave.gate.circuit import Circuit 17 | from dwave.gate.operations import X, RX, CNOT 18 | 19 | # Create a circuit by defining the number of qubits (2) 20 | circuit = Circuit(2) 21 | 22 | # Printing the circuit will print the circuits operations (currently empty). 23 | print("Circuit:", circuit.circuit) 24 | 25 | # Printing the circuit object will provide some information which can be especially useful when 26 | # using the Pyton interpreter or Jupyter. 27 | print(circuit) 28 | 29 | # We can add more qubits to the circuit manually. 30 | circuit.add_qubit() 31 | 32 | # Qubits in all registers can be accessed via 'circuit.qubits'. 33 | print("Qubits:", circuit.qubits) 34 | 35 | # We can also, similarly, add a new quantum register and add a qubit to it. 36 | circuit.add_qregister(label="my_qreg") 37 | circuit.add_qubit(qreg_label="my_qreg") 38 | print("Quantum registers:", circuit.qregisters) 39 | 40 | # Now, we should have 4 qubits in 2 registers. 41 | print("Number of qubits:", circuit.num_qubits) 42 | print("Qubits:", circuit.qubits) 43 | 44 | # To create a circuit, we use the circuit context manager and append gates as follows; 45 | # an X-gate to the first qubit (q[0]) and an X-rotation to the second qubit (q[1]). 46 | with circuit.context as (q, c): 47 | X(q[0]) 48 | RX(0.5, q[1]) 49 | 50 | print("Circuit", circuit.circuit) 51 | 52 | # Note that 'circuit.context' returns a NamedTuple which is unpacked dirctly into 'c' and 'q' above. 53 | # It would work equally well to simply keep the named tuple as e.g., 'reg' and then call the quantum 54 | # and classical registers via 'reg.q' and 'reg.c' respectively: 55 | # 56 | # with circuit.context as reg: 57 | # X(reg.q[0]) 58 | # RX(0.5, reg.q[1]) 59 | # 60 | 61 | # We now have a circuit object which we could, for example, send to run on a compatible 62 | # simulator or hardware. After the circuit has been created it is automatically locked. 63 | # This is to prevent any unexpected changes to the circuit. To unlock it, and add more 64 | # gates to it, we simply call: 65 | circuit.unlock() 66 | 67 | # We can now apply more gates which will be appended to the circuit. 68 | with circuit.context as (q, c): 69 | CNOT(q[0], q[1]) 70 | 71 | print("Circuit", circuit.circuit) 72 | 73 | # If we later wish to reuse the circuit, we can reset it. This will clear all applied operations, 74 | # but will keep the qubits and registers intact (unless 'keep_registers' is set to 'False'). 75 | circuit.reset(keep_registers=False) # 'keep_registers=True' as default 76 | 77 | print(circuit) 78 | print("Circuit", circuit.circuit) 79 | -------------------------------------------------------------------------------- /examples/1_operations.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Using and appending operations.""" 16 | from dwave.gate.circuit import Circuit 17 | import dwave.gate.operations as ops 18 | 19 | # Operations, or gates, are objects that contain information related to that specific operation, 20 | # including its matrix representation, potential decompositions, and application within a circuit. 21 | print(ops.X()) 22 | 23 | # We can call the matrix property on either the class itself or on an instance (which is an 24 | # instantiated operation, with additional parameters and/or qubit information). 25 | print("X matrix:\n", ops.X.matrix) 26 | 27 | # If the matrix representation isn't general (e.g., as for the X-gate above) and requires knowledge 28 | # of parameters, it can only be retrieved from an instance (otherwise an exception will be raised). 29 | print("Z-rotation matrix:\n", ops.RZ(4.2).matrix) 30 | 31 | # Operations are applied when either the class or an instance is called within a circuit context. 32 | # They can be applied to the circuit in several different ways, as detailed below. Both qubits and 33 | # parameters can be passed either as single values (if supported by the gate) or contained in a 34 | # sequence. 35 | circuit = Circuit(3) 36 | with circuit.context as (q, c): 37 | # apply single-qubit gate 38 | ops.Z(q[0]) 39 | # apply single-qubit gate using kwarg 40 | ops.Hadamard(qubits=q[0]) 41 | # apply a parametric gate 42 | ops.RY(4.2, q[1]) 43 | # apply a parametric gate using kwargs 44 | ops.Rotation(parameters=[4.2, 3.1, 2.3], qubits=q[1]) 45 | # apply controlled gate 46 | ops.CNOT(q[0], q[1]) 47 | # apply controlled gate using kwargs 48 | ops.CX(control=q[0], target=q[1]) 49 | # apply controlled qubit gates using slicing (must unpack) 50 | ops.CZ(*q[:2]) 51 | # apply multi-qubit (non-controlled) gates (note tuple) 52 | ops.SWAP((q[0], q[1])) 53 | # apply multi-qubit (non-controlled) gates using kwargs 54 | ops.CSWAP(qubits=(q[0], q[1], q[2])) 55 | # apply multi-qubit (non-controlled) gates using slicing 56 | ops.SWAP(q[:2]) 57 | # apply gate on all qubits in the circuit 58 | ops.Toffoli(q) 59 | 60 | print(circuit) 61 | 62 | # Print all operations in the circuit () 63 | for op in circuit.circuit: 64 | print(op) 65 | 66 | # Note that e.g., CNOT and CX apply the exact same gate. CNOT is only an alias for CX (so they both 67 | # are labelled as CX in the circuit). There are also other aliases which you can spot either in the 68 | # `dwave/gate/operations/operations.py` file, or read more about in the documentation. 69 | -------------------------------------------------------------------------------- /examples/2_templates.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Creating and using templates.""" 16 | from dwave.gate.circuit import Circuit 17 | from dwave.gate.tools import build_unitary 18 | import dwave.gate.operations as ops 19 | 20 | import numpy as np 21 | np.set_printoptions(suppress=True) 22 | 23 | # Let's create a circuit which applies a Z-gate to a single qubit, but by using 24 | # only Hadamards and X-gates. 25 | z_circuit = Circuit(1) 26 | with z_circuit.context as (q, c): 27 | ops.Hadamard(q[0]) 28 | ops.X(q[0]) 29 | ops.Hadamard(q[0]) 30 | 31 | # We can check to see if it's correct by printing its unitary using the built-in 'build_unitary' 32 | # function. The resulting matrix represents the unitary that is applied when applying these three 33 | # operations according to the circuit above. 34 | z_matrix = build_unitary(z_circuit) 35 | print("Z matrix:\n", z_matrix) 36 | 37 | # The above circuit can also be applied to other circuits. This will basically just apply the 38 | # operations within that circuit to the new circuit, mapping the old qubits to the new. 39 | circuit = Circuit(3) 40 | with circuit.context as (q, c): 41 | z_circuit(q[1]) 42 | 43 | print("Circuit:", circuit.circuit) 44 | 45 | # If the circuit is meant to be used as a custom operation, the 'create_operation' function can be 46 | # used to transform the circuit in to an operation class, inheriting from the abstract 'Operations' 47 | # class (as all other operations do). 48 | from dwave.gate.operations import create_operation 49 | 50 | # If no name is given, the custom operation class will simply be named "CustomOperation". 51 | MyZOperation = create_operation(z_circuit, name="MyZOperation") 52 | 53 | print(MyZOperation()) 54 | 55 | # We can compare its matrix representation to the built in 'Z' operation. 56 | print("MyZOperation matrix:\n", MyZOperation.matrix) 57 | print("Z matrix:\n", ops.Z.matrix) 58 | assert np.allclose(MyZOperation.matrix, ops.Z.matrix) 59 | 60 | # This custom gate can now be applied to the circuit in exactly the same way as any other gate. Note 61 | # that if we don't reset the circuit, the 'z_circuit' operations will still be there. 62 | circuit.reset() 63 | with circuit.context as (q, c): 64 | MyZOperation(q[2]) 65 | 66 | print("Circuit:", circuit.circuit) 67 | -------------------------------------------------------------------------------- /examples/3_parametric_circuits.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Creating and using templates.""" 16 | from dwave.gate.circuit import Circuit, ParametricCircuit 17 | from dwave.gate.tools import build_unitary 18 | import dwave.gate.operations as ops 19 | 20 | import numpy as np 21 | np.set_printoptions(suppress=True) 22 | 23 | # Let's create a parametric circuit which applies a rotation gate to a single qubit 24 | rot_circuit = ParametricCircuit(1) 25 | with rot_circuit.context as (p, q, c): 26 | ops.RZ(p[0], q[0]) 27 | ops.RY(p[1], q[0]) 28 | ops.RZ(p[2], q[0]) 29 | 30 | # The above circuit can be applied to other, non-parametric, circuits. This will just apply the 31 | # operations within that circuit to the new circuit, mapping the old qubits to the new. Since it's a 32 | # parametric circuit, the parameters need to be passed when calling it, similarly to how parametric 33 | # operations work. 34 | circuit = Circuit(3) 35 | with circuit.context as (q, c): 36 | rot_circuit([np.pi, np.pi / 2, np.pi], q[1]) 37 | 38 | print("Circuit:", circuit.circuit) 39 | 40 | # If the parametric circuit is meant to be used as a custom operation, the 'create_operation' 41 | # function can be used to transform the circuit in to an parametric operation class, inheriting from 42 | # the abstract 'ParametricOperations' class (as all other parametric operations do). 43 | from dwave.gate.operations import create_operation 44 | 45 | # If no name is given, the custom operation class will simply be named "CustomOperation". 46 | MyRotOperation = create_operation(rot_circuit, name="MyRotOperation") 47 | 48 | print(MyRotOperation([0, 1, 3.14])) 49 | 50 | # We can compare its matrix representation to the built in 'Rotation' operation. 51 | params = [1.2, 2.3, 3.4] 52 | print("MyRotOperation matrix:\n", MyRotOperation(params).matrix) 53 | print("Rotation matrix:\n", ops.Rotation(params).matrix) 54 | assert np.allclose(MyRotOperation(params).matrix, ops.Rotation(params).matrix) 55 | 56 | # This custom gate can now be applied to the circuit in exactly the same way as any other gate. Note 57 | # that if we don't reset the circuit, the 'rot_circuit' operations will still be there. 58 | circuit.reset() 59 | with circuit.context as (q, c): 60 | MyRotOperation([3 * np.pi / 2, np.pi / 2, np.pi], q[2]) 61 | 62 | print("Circuit:", circuit.circuit) 63 | -------------------------------------------------------------------------------- /examples/4_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Running circuits on the simulator.""" 16 | 17 | import numpy as np 18 | np.set_printoptions(suppress=True) 19 | 20 | import dwave.gate.operations as ops 21 | 22 | from dwave.gate.circuit import Circuit 23 | from dwave.gate.simulator.simulator import simulate 24 | 25 | # Let's build a simple circuit that applies an X gate to the first qubit, and then a CNOT gate on 26 | # both qubits, which will deterministically also apply an X gate to the second qubit since it's 27 | # controlled by the first. 28 | circuit = Circuit(2) 29 | 30 | with circuit.context as (q, c): 31 | ops.X(q[0]) 32 | ops.CNOT(q[0], q[1]) 33 | 34 | print("Circuit", circuit.circuit) 35 | 36 | # We can now simulate this circuit and print the circuits state-vector. 37 | simulate(circuit) 38 | 39 | # Note that it returns the vector representing the state |11>, which is expected. 40 | print(circuit.state) 41 | 42 | # Using the tools and operations explained in the previous examples, arbitrary circuits can be 43 | # constructed and simulated, returning the final state. Note also that the state is always 44 | # initialized to all 0 (e.g., in the case above, |00>). This can be altered by applying the gates 45 | # necessary to create the wanted state. 46 | -------------------------------------------------------------------------------- /examples/5_measurements.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Measuring circuits on the simulator.""" 16 | 17 | import numpy as np 18 | np.set_printoptions(suppress=True) 19 | 20 | import dwave.gate.operations as ops 21 | 22 | from dwave.gate.circuit import Circuit 23 | from dwave.gate.simulator.simulator import simulate 24 | 25 | # Let's again build a simple circuit that applies a Hadamard gate to the first qubit and then a CNOT gate 26 | # on both qubits, but this time we measure the second qubit instead of simply returning the state. 27 | 28 | # To store the measured value, a classical register is required. 29 | circuit = Circuit(2, 1) 30 | 31 | # The classical register can be accessed via the context manager, commonly denoted 'c'. The 32 | # measurement can then be piped over to the classical register and stored in a bit object. 33 | with circuit.context as (q, c): 34 | ops.Hadamard(q[0]) 35 | ops.CNOT(q[0], q[1]) 36 | m = ops.Measurement(q[1]) | c[0] 37 | 38 | print("Circuit", circuit.circuit) 39 | 40 | # We can simulate this circuit as usual which will return it's state-vector, but due to the 41 | # measurement the state will collapse into a substate. The resulting state after running the above 42 | # circuit depends on the measurement outcome since the state before the measurment is entangled. 43 | simulate(circuit) 44 | print(circuit.state) 45 | 46 | # We can easily check the classical register, and the containing bits, to see which value was 47 | # measured. Since we measured the first bit in the register, we choose it in the register. 48 | print(circuit.bits[0].value) 49 | 50 | # Since we've stored the measurement operation in 'm' we can use it to check the measured 51 | # qubits, bit values and the state at measurement. 52 | print(f"Bits: {m.bits}") 53 | print(f"State: {m.state}") 54 | 55 | # The measurement operation also allows for further sampling... 56 | print(m.sample()) 57 | 58 | # ...and expectation value calculations. 59 | print(m.expval()) 60 | 61 | # Note that the state, sampling and expectation values are only accessible on simulators and cannot 62 | # be retrieved if the circuit would be run on quantum hardware. -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set PYTHON=python3 4 | set report=html 5 | set COVERAGE=--cov=dwave.gate --cov-report=%report% 6 | set TESTRUNNER=-m pytest tests 7 | 8 | IF /I "%1"=="install" GOTO install 9 | IF /I "%1"=="clean" GOTO clean 10 | IF /I "%1"=="test" GOTO test 11 | IF /I "%1"=="coverage" GOTO coverage 12 | IF /I "%1"=="docs" GOTO docs 13 | IF /I "%1"=="clean-docs" GOTO clean-docs 14 | IF /I "%1"=="format" GOTO format 15 | GOTO error 16 | 17 | :install 18 | CALL %PYTHON% -m pip install . 19 | CALL %PYTHON% .\dwave\gate\simulator\operation_generation.py 20 | CALL %PYTHON% setup.py build_ext --inplace 21 | GOTO :EOF 22 | 23 | :clean 24 | IF EXIST ".pytest_cache/" RMDIR /S /Q ".pytest_cache/" 25 | IF EXIST "dwave_gate.egg-info/" RMDIR /S /Q "dwave_gate.egg-info/" 26 | IF EXIST "dist/" RMDIR /S /Q "dist/" 27 | IF EXIST "build/" RMDIR /S /Q "build/" 28 | GOTO :EOF 29 | 30 | :test 31 | CALL %PYTHON% %TESTRUNNER% 32 | GOTO :EOF 33 | 34 | :coverage 35 | CALL %PYTHON% %TESTRUNNER% %COVERAGE% 36 | GOTO :EOF 37 | 38 | :docs 39 | CALL .\docs\make.bat html 40 | GOTO :EOF 41 | 42 | :clean-docs 43 | CALL .\docs\make.bat clean 44 | GOTO :EOF 45 | 46 | :format 47 | CALL black -l 100 .\dwave\gate .\tests 48 | CALL isort -l 100 --profile black .\dwave\gate .\tests 49 | GOTO :EOF 50 | 51 | :error 52 | IF "%1"=="" ( 53 | ECHO make: *** No targets specified and no makefile found. Stop. 54 | ) ELSE ( 55 | ECHO make: *** No rule to make target '%1%'. Stop. 56 | ) 57 | GOTO :EOF 58 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | PYTHON3 := $(shell which python3 2>/dev/null) 16 | BLACK := $(shell which black 2>/dev/null) 17 | ISORT := $(shell which isort 2>/dev/null) 18 | 19 | PYTHON := python3 20 | 21 | report ?= html # HTML is the default report type 22 | COVERAGE := --cov=dwave.gate --cov-report=$(report) 23 | 24 | TESTRUNNER := -m pytest tests 25 | 26 | .PHONY: install 27 | install: 28 | $(PYTHON) dwave/gate/simulator/operation_generation.py 29 | $(PYTHON) setup.py build_ext --inplace 30 | $(PYTHON) -m pip install . 31 | 32 | # whether coverage files should be removed (true) 33 | # default is to not delete coverage files (false) 34 | cov := false 35 | 36 | .PHONY: clean 37 | clean: 38 | rm -rf .pytest_cache/ 39 | rm -rf *.egg-info/ 40 | rm -rf dist/ build/ 41 | 42 | ifeq ($(cov),true) 43 | rm -rf coverage_html/ 44 | rm -f .coverage coverage.* 45 | endif 46 | 47 | .PHONY: test 48 | test: 49 | $(PYTHON) $(TESTRUNNER) 50 | 51 | .PHONY: coverage 52 | coverage: 53 | $(PYTHON) $(TESTRUNNER) $(COVERAGE) 54 | 55 | .PHONY: docs 56 | docs: 57 | make -C docs/ html 58 | 59 | .PHONY: clean-docs 60 | clean-docs: 61 | make -C docs/ clean 62 | 63 | .PHONY: format 64 | format: 65 | ifndef ISORT 66 | @echo "D-Wave Gate Model software uses isort to sort imports." 67 | endif 68 | isort -l 100 --profile black ./dwave/gate ./tests 69 | ifndef BLACK 70 | @echo "D-Wave Gate Model software uses the Black formatter." 71 | endif 72 | black -l 100 ./dwave/gate ./tests 73 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=46.4.0", # PEP-420 support, PEP-517/518 support 4 | "wheel>=0.30.0", # limited python api support 5 | "cython~=3.0", 6 | "numpy~=2.0", 7 | ] 8 | build-backend = "setuptools.build_meta" 9 | 10 | [project] 11 | name = "dwave-gate" 12 | dynamic = ["version"] 13 | authors = [ 14 | {name = "D-Wave Inc.", email = "tools@dwavesys.com"}, 15 | ] 16 | description = "Gate model library." 17 | license = {file = "LICENSE"} 18 | classifiers = [ 19 | "Operating System :: Microsoft :: Windows", 20 | "Operating System :: POSIX", 21 | "Operating System :: Unix", 22 | "Operating System :: MacOS", 23 | "Programming Language :: Python", 24 | "Programming Language :: Python :: 3", 25 | "Programming Language :: Python :: 3.9", 26 | "Programming Language :: Python :: 3.10", 27 | "Programming Language :: Python :: 3.11", 28 | "Programming Language :: Python :: 3.12", 29 | "Programming Language :: Python :: 3.13", 30 | "Programming Language :: Python :: 3 :: Only", 31 | ] 32 | requires-python = ">= 3.9" 33 | dependencies = [ 34 | "numpy>=1.24.4", 35 | ] 36 | [project.optional-dependencies] 37 | qir = [ 38 | "pyqir>=0.8.0,<0.10", 39 | ] 40 | 41 | [project.readme] 42 | file = "README.rst" 43 | content-type = "text/x-rst" 44 | 45 | [project.urls] 46 | Homepage = "https://github.com/dwavesystems/dwave-gate" 47 | Download = "https://github.com/dwavesystems/dwave-gate/releases" 48 | 49 | [tool.cibuildwheel] 50 | build-verbosity = "1" 51 | skip = "pp* *musllinux*" 52 | before-test = "pip install pyqir==0.9.0 && pip install -r {project}/requirements_dev.txt" 53 | test-command = "pytest {project}/tests" 54 | before-build = "pip install cgen==2020.1 && python {project}/dwave/gate/simulator/operation_generation.py" 55 | 56 | [tool.cibuildwheel.linux] 57 | archs = "x86_64 aarch64" 58 | manylinux-x86_64-image = "manylinux2014" 59 | manylinux-aarch64-image = "manylinux2014" 60 | 61 | [tool.cibuildwheel.macos] 62 | # We follow NumPy and don't build universal wheels, see https://github.com/numpy/numpy/pull/20787 63 | archs = "x86_64 arm64" 64 | 65 | [tool.cibuildwheel.windows] 66 | archs = "AMD64" 67 | # before-build = "pip install delvewheel" 68 | # repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" 69 | 70 | [[tool.cibuildwheel.overrides]] 71 | select = "*aarch64*" 72 | before-test = "pip install -r {project}/requirements_dev.txt" 73 | 74 | [tool.pyright] 75 | reportUnusedExpression = false 76 | 77 | [tool.pytest.ini_options] 78 | minversion = "6.0" 79 | addopts = "-ra -q" 80 | testpaths = [ 81 | "tests", 82 | ] 83 | 84 | [tool.setuptools.packages.find] 85 | include = ["dwave.*"] 86 | 87 | [tool.setuptools.dynamic] 88 | version = {attr = "dwave.gate.__version__"} 89 | -------------------------------------------------------------------------------- /releasenotes/notes/add-get-qubit-method-c854f57d84719a8f.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds the ``get_qubit`` method to the Circuit class allowing for the retrieval of qubits in the circuit by label. 5 | 6 | .. code-block:: python 7 | 8 | circuit = Circuit() 9 | circuit.add_qubit(Qubit("my_qubit")) 10 | 11 | qubit = circuit.get_qubit("my_qubit") 12 | -------------------------------------------------------------------------------- /releasenotes/notes/add-measurements-c548833d86d764a8.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: > 3 | We began using `Reno `_ as a changelog tool after the first 4 | release, version 0.1.0. Features that were part of that release are not included in the 5 | changelog. 6 | features: 7 | - Add support for mid-circuit measurements. 8 | - Add support for operations conditioned on measurements. 9 | - Add support for measurement sampling and expectation value calculations. 10 | 11 | -------------------------------------------------------------------------------- /releasenotes/notes/context-returns-namedtuple-83bda2334a7fb8d1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Circuit context returns the classical register together with ``QuantumRegister``. The two 5 | registers are contained within a tuple and are returned as ``with circuit.context as (q, c)``, 6 | instead of ``with circuit.context as q``. 7 | 8 | .. code-block:: python 9 | 10 | circuit = Circuit(2, 2) 11 | 12 | with circuit.context as (q, c): 13 | ops.X(q[0]) 14 | 15 | upgrade: 16 | - | 17 | Update context to return a ``NamedTuple`` instead of a regular tuple, allowing for the registers 18 | to be returned as previously, directly unpacked into ``q`` and ``c``, or as a named ``Registers`` 19 | tuple and retrieved via ``Registers.q`` and ``Registers.c`` respectively. 20 | - | 21 | Update parametric context to return a ``NamedTuple`` instead of a regular tuple similar to the 22 | non-parametric context upgrade, with the parameter register accessed via ``Registers.p``. 23 | 24 | .. code-block:: python 25 | 26 | circuit = ParametricCircuit(2, 2) 27 | 28 | with circuit.context as reg: 29 | ops.RX(reg.p[0], reg.q[0]) 30 | 31 | -------------------------------------------------------------------------------- /releasenotes/notes/deprecate-python3.7--102cd953fb6e9a09.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | deprecations: 3 | - Support for Python 3.7 deprecated. 4 | -------------------------------------------------------------------------------- /releasenotes/notes/feature-python312-cython3-f79ec6b10494fdc3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - Add support and build wheels for Python 3.12. 4 | - Build C++ extensions with Cython 3. 5 | 6 | -------------------------------------------------------------------------------- /releasenotes/notes/feature-qir-compiler-58332c0019b51ae4.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Adds support for compiling circuits into the Quantum Intermediate Representation (QIR) 5 | language using PyQIR API and qirlib LLVM wrapper. 6 | 7 | .. code-block:: python 8 | 9 | circuit = Circuit(2) 10 | 11 | with circuit.context as reg: 12 | ops.X(reg.q[0]) 13 | ops.Y(reg.q[1]) 14 | 15 | qir_string = circuit.to_qir() 16 | 17 | Circuits can also be compiled directly into bitcode. 18 | 19 | .. code-block:: python 20 | 21 | qir_string = circuit.to_qir(bitcode=True) 22 | 23 | - | 24 | Adds a Quantum Intermediate Representation (QIR) loader which consumes a QIR script and returns 25 | a corresponding circuit containing the same instruction. 26 | 27 | .. code-block:: 28 | 29 | ; ModuleID = 'Citrus' 30 | source_filename = "Citrus" 31 | 32 | %Qubit = type opaque 33 | 34 | define void @main() { 35 | entry: 36 | call void @__quantum__rt__initialize(i8* null) 37 | call void @__quantum__qis__x__body(%Qubit* null) 38 | call void @__quantum__qis__y__body(%Qubit* inttoptr (i64 1 to %Qubit*)) 39 | ret void 40 | } 41 | 42 | declare void @__quantum__rt__initialize(i8*) 43 | declare void @__quantum__qis__x__body(%Qubit*) 44 | declare void @__quantum__qis__y__body(%Qubit*) 45 | 46 | The above QIR script can be loaded into a dwave-gate circuit using the 47 | ``dwave.gate.qir.loader.load_qir_string`` function. 48 | 49 | .. code-block:: python 50 | 51 | from dwave.gate.qir.loader import load_qir_string 52 | 53 | circuit = load_qir_string(qir_string, circuit=circuit) -------------------------------------------------------------------------------- /releasenotes/notes/fix-cond-not-propagating-5dd63d14836aa27e.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Updates the circuit so that conditional parametric gates are evaluated in place when appended to 5 | the active circuit, fixing an issue where changes to these gates post application are not 6 | propagated to the circuit. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-measurement-storage-issue-ab52cc05d0802876.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fixes an issue where an error is being raised when subsequent measurements are made on the same 5 | qubit. Instead, any subsequent measurement will replace the former one at simulation runtime. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-open-issues-6b777eed9cf72188.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Check types when adding qubits/bits to a circuit and raise a ``TypeError`` if an incorrect type 5 | is passed to the method. 6 | - Rename ``label`` to ``name`` for operations. 7 | fixes: 8 | - Fix inconsistent spacing in primitive object representations. 9 | - Fix sequential qubit/bit labelling when adding new registers. 10 | - Update operation representations to use ``repr()`` for hashables. 11 | 12 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-operation-call-return-0d522322ccf0f217.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - Fix return type from ``Operation.__call__`` and subclasses. -------------------------------------------------------------------------------- /releasenotes/notes/fix-qir-load-midfunc-return-b0f952734e3c6f2a.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - Fixes loading QIR scripts with a return mid-function. -------------------------------------------------------------------------------- /releasenotes/notes/fix-sample-circular-import-86ae100745b87464.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - Fix circular import issue when importing the simulator prior operations. 4 | -------------------------------------------------------------------------------- /releasenotes/notes/pyqir-support-a682839ae2ccc0c5.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Remove PyQIR version check as it's been added as an optional dependency. A supported 5 | version can now be installed directly with ``pip install dwave-gate[qir]``. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/python-3.9-3.13-9b276ed8d1edfdf5.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - Support Python 3.13 4 | upgrade: 5 | - Remove support for Python 3.8 6 | -------------------------------------------------------------------------------- /releasenotes/notes/support-numpy-2.0-d9eae4d898fe217e.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - Support, and build with, NumPy 2.0 4 | -------------------------------------------------------------------------------- /releasenotes/notes/switch-to-native-namespaces-e8a771fc8003599f.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Switch from pkgutil-style namespace package to native namespace package (PEP 420). 5 | While native namespace packages and pkgutil-style namespace packages are 6 | largely compatible, we recommend using only native ones going forward. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/update-makefiles-cd19366de0add7d7.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - Add a Windows executable makefile (``make.bat``) 4 | - | 5 | Add ``make docs`` and ``make clean-docs`` commands to the makefile for building and 6 | cleaning/removing the documentation locally. 7 | fixes: 8 | - Adds ``pip`` installation step to ``make install`` command. 9 | - | 10 | Expand installation section in the README to include installation on Windows (using 11 | ``make.bat``) and add details to installation steps. 12 | 13 | -------------------------------------------------------------------------------- /releasenotes/notes/upgrade-circuit-call-bits-f308cc926fa3bce3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Upgrade circuit call to accept classical bits in which to store measurement values. If no bits 5 | are passed to the circuit call, measurements will not be stored when circuit is simulated. 6 | 7 | .. code-block:: python 8 | 9 | circuit = Circuit(2, 2) 10 | 11 | with circuit.context as (q, c): 12 | ops.Hadamard(q[0]) 13 | ops.Hadamard(q[1]) 14 | ops.Measurement(q) | c 15 | 16 | circuit_2 = Circuit(2, 2) 17 | 18 | with circuit_2.context as (q, c): 19 | circuit(q, c) # pass bits to 'circuit' -------------------------------------------------------------------------------- /releasenotes/notes/upgrade-multi-qubit-sampling-89e654a179975106.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Adds support for multi-qubit sampling in measurements. ``Measurement.sample()`` and 5 | ``Measurement.expval()`` are updated to accept a sequence of qubit indices to sample, sampling 6 | all measured qubits if none are given. 7 | 8 | .. code-block:: python 9 | 10 | circuit = Circuit(2, 2) 11 | 12 | with circuit.context as (q, c): 13 | ops.X(q[0]) 14 | m = ops.Measurement(q) | c 15 | 16 | simulate(circuit) 17 | 18 | m.sample(num_samples=3, as_bitstring=True) 19 | # ['10', '10', '10'] 20 | 21 | features: 22 | - Measurement samples can be returned as bitstrings instead of integers in nested lists. 23 | 24 | -------------------------------------------------------------------------------- /releasenotes/notes/upgrade-simulator-return-90e29e50768b7112.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | A density matrix representation of the state can be accessed via the ``Circuit.density_matrix`` 5 | property, for both pure and mixed states (with the former being lazily calculated from the state 6 | vector). 7 | 8 | upgrade: 9 | - | 10 | The state is now stored in the ``Circuit`` object (same as the bits/measurement results) instead 11 | of being returned by the ``simulate()`` function. It can now be accessed via the 12 | ``Circuit.state`` property. 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # working environment for Python>=3.9,<=3.12 2 | numpy==2.0.2 3 | cython==3.0.11 4 | cgen==2020.1 5 | pyqir==0.9.0 6 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pytest>=8.1.1 # native namespace package support added in 8.1.1 2 | pytest-cov>=4.0.0 3 | pytest-mock>=3.10.0 4 | typing_extensions>=4.4.0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | from setuptools import setup, Extension 17 | from setuptools.command.build_ext import build_ext 18 | 19 | import numpy 20 | from Cython.Build import cythonize 21 | 22 | 23 | extra_compile_args = { 24 | 'msvc': ['/std:c++17'], 25 | 'unix': ['-std=c++17'], 26 | } 27 | 28 | extra_link_args = { 29 | 'msvc': [], 30 | 'unix': [], 31 | } 32 | 33 | 34 | class build_ext_compiler_check(build_ext): 35 | def build_extensions(self): 36 | compiler = self.compiler.compiler_type 37 | 38 | compile_args = extra_compile_args[compiler] 39 | for ext in self.extensions: 40 | ext.extra_compile_args = compile_args 41 | 42 | link_args = extra_link_args[compiler] 43 | for ext in self.extensions: 44 | ext.extra_link_args = link_args 45 | 46 | build_ext.build_extensions(self) 47 | 48 | 49 | setup( 50 | ext_modules=cythonize( 51 | [Extension("dwave.gate.simulator.simulator", 52 | ["dwave/gate/simulator/simulator.pyx"]), 53 | Extension("dwave.gate.registers.cyregister", 54 | ["dwave/gate/registers/cyregister.pyx"]), 55 | ], 56 | annotate=bool(os.getenv("CYTHON_ANNOTATE", False)) 57 | ), 58 | include_dirs=[numpy.get_include()], 59 | cmdclass={"build_ext": build_ext_compiler_check}, 60 | ) 61 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Type 16 | 17 | import pytest 18 | 19 | import dwave.gate.operations as ops 20 | from dwave.gate.circuit import Circuit, ParametricCircuit 21 | from dwave.gate.operations.base import ControlledOperation, Operation, ParametricOperation 22 | from dwave.gate.primitives import Bit, Qubit 23 | from dwave.gate.registers import ClassicalRegister, QuantumRegister 24 | 25 | 26 | @pytest.fixture(scope="function") 27 | def dummy_function(): 28 | """Dummy function.""" 29 | return lambda _: None 30 | 31 | 32 | @pytest.fixture(scope="function") 33 | def two_qubit_circuit(): 34 | """Circuit with two qubits and three operations.""" 35 | circuit = Circuit(2) 36 | 37 | with circuit.context as regs: 38 | ops.Hadamard(regs.q[0]) 39 | ops.CNOT(regs.q[0], regs.q[1]) 40 | ops.Hadamard(regs.q[1]) 41 | 42 | return circuit 43 | 44 | 45 | @pytest.fixture(scope="function") 46 | def two_qubit_parametric_circuit(): 47 | """ParametricCircuit with two qubits and two operation.""" 48 | circuit = ParametricCircuit(2) 49 | 50 | with circuit.context as regs: 51 | ops.RX(regs.p[0], regs.q[0]) 52 | ops.RY(regs.p[1], regs.q[1]) 53 | 54 | return circuit 55 | 56 | 57 | @pytest.fixture(scope="function") 58 | def two_bit_circuit(): 59 | """Circuit with two bits and one qubit and a single operation.""" 60 | circuit = Circuit(1, 2) 61 | with circuit.context as regs: 62 | ops.X(regs.q[0]) 63 | 64 | return circuit 65 | 66 | 67 | @pytest.fixture(scope="function") 68 | def empty_circuit(): 69 | """Empty circuit with no qubits, bits or operations.""" 70 | circuit = Circuit() 71 | return circuit 72 | 73 | 74 | @pytest.fixture(scope="function") 75 | def empty_parametric_circuit(): 76 | """Empty parametric circuit with no qubits, bits or operations.""" 77 | circuit = ParametricCircuit() 78 | return circuit 79 | 80 | 81 | @pytest.fixture(scope="function") 82 | def two_qubit_op(monkeypatch): 83 | """Empty two-qubit operation.""" 84 | 85 | class DummyOp(Operation): 86 | _num_qubits: int = 2 87 | 88 | monkeypatch.setattr(DummyOp, "__abstractmethods__", set()) 89 | 90 | return DummyOp 91 | 92 | 93 | @pytest.fixture(scope="function") 94 | def two_qubit_parametric_op(monkeypatch): 95 | """Empty two-qubit parametric operation.""" 96 | 97 | class DummyOp(ParametricOperation): 98 | _num_qubits: int = 2 99 | _num_params: int = 1 100 | 101 | monkeypatch.setattr(DummyOp, "__abstractmethods__", set()) 102 | 103 | return DummyOp 104 | 105 | 106 | @pytest.fixture(scope="function") 107 | def two_qubit_controlled_op(monkeypatch): 108 | """Empty two-qubit controlled operation.""" 109 | 110 | class DummyOp(ControlledOperation): 111 | _num_control: int = 1 112 | _num_target: int = 1 113 | _target_operation: Type[Operation] = ops.X 114 | 115 | monkeypatch.setattr(DummyOp, "__abstractmethods__", set()) 116 | 117 | return DummyOp 118 | 119 | 120 | @pytest.fixture() 121 | def classical_register(): 122 | """Classical register with 4 bits.""" 123 | creg = ClassicalRegister([Bit(i) for i in range(4)]) 124 | return creg 125 | 126 | 127 | @pytest.fixture() 128 | def quantum_register(): 129 | """Quantum register with 4 bits.""" 130 | qreg = QuantumRegister([Qubit(i) for i in range(4)]) 131 | return qreg 132 | -------------------------------------------------------------------------------- /tests/test_io/test_openqasm.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from inspect import cleandoc 16 | 17 | import pytest 18 | 19 | import dwave.gate.operations as ops 20 | from dwave.gate.circuit import Circuit, CircuitError, ParametricCircuit 21 | from dwave.gate.operations.base import create_operation 22 | from dwave.gate.primitives import Bit, Qubit 23 | from dwave.gate.registers.registers import ClassicalRegister, QuantumRegister 24 | 25 | 26 | class TestCircuitOpenQASM: 27 | """Unittests for generating OpenQASM 2.0 from a circuit.""" 28 | 29 | def test_circuit(self): 30 | """Test generating OpenQASM 2.0 for a circuit.""" 31 | circuit = Circuit(3) 32 | circuit.add_cregister(2, "apple") 33 | 34 | with circuit.context as regs: 35 | ops.X(regs.q[0]) 36 | ops.RX(0.42, regs.q[1]) 37 | ops.CNOT(regs.q[2], regs.q[0]) 38 | 39 | assert circuit.to_qasm() == cleandoc( 40 | """ 41 | OPENQASM 2.0; 42 | include "qelib1.inc"; 43 | 44 | qreg q[3]; 45 | creg c[2]; 46 | 47 | x q[0]; 48 | rx(0.42) q[1]; 49 | cx q[2], q[0]; 50 | """ 51 | ) 52 | 53 | def test_circuit_several_qreg(self): 54 | """Test generating OpenQASM 2.0 for a circuit with several quantum registers.""" 55 | circuit = Circuit(1) 56 | circuit.add_qregister(2, "apple") 57 | 58 | with circuit.context as regs: 59 | ops.X(regs.q[0]) 60 | ops.RX(0.42, regs.q[1]) 61 | ops.CNOT(regs.q[2], regs.q[0]) 62 | 63 | assert circuit.to_qasm() == cleandoc( 64 | """ 65 | OPENQASM 2.0; 66 | include "qelib1.inc"; 67 | 68 | qreg q0[1]; 69 | qreg q1[2]; 70 | 71 | x q0[0]; 72 | rx(0.42) q1[0]; 73 | cx q1[1], q0[0]; 74 | """ 75 | ) 76 | 77 | def test_circuit_several_creg(self): 78 | """Test generating OpenQASM 2.0 for a circuit with several classical registers.""" 79 | circuit = Circuit(2) 80 | circuit.add_cregister(2, "apple") 81 | circuit.add_cregister(3, "banana") 82 | 83 | with circuit.context as regs: 84 | ops.X(regs.q[0]) 85 | ops.RX(0.42, regs.q[1]) 86 | ops.CNOT(regs.q[1], regs.q[0]) 87 | 88 | assert circuit.to_qasm() == cleandoc( 89 | """ 90 | OPENQASM 2.0; 91 | include "qelib1.inc"; 92 | 93 | qreg q[2]; 94 | creg c0[2]; 95 | creg c1[3]; 96 | 97 | x q[0]; 98 | rx(0.42) q[1]; 99 | cx q[1], q[0]; 100 | """ 101 | ) 102 | 103 | def test_circuit_reg_labels(self): 104 | """Test generating OpenQASM 2.0 for a circuit using reg labels.""" 105 | circuit = Circuit(1) 106 | circuit.add_qregister(2, "apple") 107 | circuit.add_cregister(2, "banana") 108 | 109 | with circuit.context as regs: 110 | ops.X(regs.q[0]) 111 | ops.RX(0.42, regs.q[1]) 112 | ops.CNOT(regs.q[2], regs.q[0]) 113 | 114 | assert circuit.to_qasm(reg_labels=True) == cleandoc( 115 | """ 116 | OPENQASM 2.0; 117 | include "qelib1.inc"; 118 | 119 | qreg qreg0[1]; 120 | qreg apple[2]; 121 | creg banana[2]; 122 | 123 | x qreg0[0]; 124 | rx(0.42) apple[0]; 125 | cx apple[1], qreg0[0]; 126 | """ 127 | ) 128 | 129 | def test_circuit_gate_definitions(self): 130 | """Test generating OpenQASM 2.0 for a circuit using gate definitions.""" 131 | circuit = Circuit(3) 132 | 133 | with circuit.context as regs: 134 | ops.X(regs.q[0]) 135 | ops.Rotation((0.1, 0.2, 0.3), regs.q[1]) 136 | ops.CNOT(regs.q[2], regs.q[0]) 137 | 138 | assert circuit.to_qasm(gate_definitions=True) == cleandoc( 139 | """ 140 | OPENQASM 2.0; 141 | include "qelib1.inc"; 142 | 143 | gate rot(beta, gamma, delta) { rz(beta) q[0]; ry(gamma) q[0]; rz(delta) q[0]; } 144 | 145 | qreg q[3]; 146 | 147 | x q[0]; 148 | rot(0.1, 0.2, 0.3) q[1]; 149 | cx q[2], q[0]; 150 | """ 151 | ) 152 | 153 | def test_parametric_circuit(self): 154 | """Test generating OpenQASM 2.0 for a parametric circuit.""" 155 | 156 | circuit = ParametricCircuit(3) 157 | 158 | with circuit.context as regs: 159 | ops.X(regs.q[0]) 160 | ops.RX(regs.p[0], regs.q[1]) 161 | ops.CNOT(regs.q[2], regs.q[0]) 162 | 163 | with pytest.raises( 164 | CircuitError, match="Parametric circuits cannot be transpiled into OpenQASM." 165 | ): 166 | circuit.to_qasm() 167 | 168 | 169 | class TestOperationsOpenQASM: 170 | """Unittests for generating OpenQASM 2.0 from operations.""" 171 | 172 | @pytest.mark.parametrize( 173 | "op, openqasm", 174 | [ 175 | (ops.Identity(), "id"), 176 | (ops.X(), "x"), 177 | (ops.Y(), "y"), 178 | (ops.Z(), "z"), 179 | (ops.Hadamard(), "h"), 180 | (ops.T(), "t"), 181 | (ops.Phase(), "s"), 182 | (ops.RX(2), "rx(2)"), 183 | (ops.RY(2), "ry(2)"), 184 | (ops.RZ(2), "rz(2)"), 185 | (ops.Rotation((1, 2, 3)), "rot(1, 2, 3)"), 186 | (ops.CX(), "cx"), 187 | (ops.CY(), "cy"), 188 | (ops.CNOT(), "cx"), 189 | (ops.CZ(), "cz"), 190 | (ops.CHadamard(), "ch"), 191 | (ops.CRX(1), "crx(1)"), 192 | (ops.CRY(2), "cry(2)"), 193 | (ops.CRZ(4.2), "crz(4.2)"), 194 | (ops.CRotation((1, 2, 4.2)), "crot(1, 2, 4.2)"), 195 | (ops.SWAP(), "swap"), 196 | (ops.CSWAP(), "cswap"), 197 | (ops.CCNOT(), "ccx"), 198 | ], 199 | ) 200 | def test_gate(self, op, openqasm): 201 | """Test generating OpenQASM 2.0 for a gate without qubits.""" 202 | assert op.to_qasm() == openqasm 203 | 204 | @pytest.mark.parametrize( 205 | "op, openqasm", 206 | [ 207 | (ops.Identity(Qubit(0)), "id q[0]"), 208 | (ops.X(Qubit(0)), "x q[0]"), 209 | (ops.Y(Qubit(0)), "y q[0]"), 210 | (ops.Z(Qubit(0)), "z q[0]"), 211 | (ops.Hadamard(Qubit(0)), "h q[0]"), 212 | (ops.T(Qubit(0)), "t q[0]"), 213 | (ops.Phase(Qubit(0)), "s q[0]"), 214 | (ops.RX(2, Qubit(0)), "rx(2) q[0]"), 215 | (ops.RY(2, Qubit(0)), "ry(2) q[0]"), 216 | (ops.RZ(2, Qubit(0)), "rz(2) q[0]"), 217 | (ops.Rotation((1, 2, 3), Qubit(0)), "rot(1, 2, 3) q[0]"), 218 | (ops.CX(Qubit(0), Qubit(1)), "cx q[0], q[1]"), 219 | (ops.CY(Qubit(0), Qubit(1)), "cy q[0], q[1]"), 220 | (ops.CNOT(Qubit(0), Qubit(1)), "cx q[0], q[1]"), 221 | (ops.CZ(Qubit(0), Qubit(1)), "cz q[0], q[1]"), 222 | (ops.CHadamard(Qubit(0), Qubit(1)), "ch q[0], q[1]"), 223 | (ops.CRX(1, Qubit(0), Qubit(1)), "crx(1) q[0], q[1]"), 224 | (ops.CRY(2, Qubit(0), Qubit(1)), "cry(2) q[0], q[1]"), 225 | (ops.CRZ(4.2, Qubit(0), Qubit(1)), "crz(4.2) q[0], q[1]"), 226 | (ops.CRotation((1, 2, 4.2), Qubit(0), Qubit(1)), "crot(1, 2, 4.2) q[0], q[1]"), 227 | (ops.SWAP((Qubit(0), Qubit(1))), "swap q[0], q[1]"), 228 | (ops.CSWAP((Qubit(0), Qubit(1), Qubit(2))), "cswap q[0], q[1], q[2]"), 229 | (ops.CCNOT((Qubit(0), Qubit(1), Qubit(2))), "ccx q[0], q[1], q[2]"), 230 | ], 231 | ) 232 | def test_gate_qubits(selfop, op, openqasm): 233 | """Test generating OpenQASM 2.0 for a gate with qubits.""" 234 | assert op.to_qasm() == openqasm 235 | 236 | def test_create_operation(self): 237 | """Test creating an operation.""" 238 | circuit = Circuit(1) 239 | 240 | with circuit.context as regs: 241 | ops.Hadamard(regs.q[0]) 242 | ops.X(regs.q[0]) 243 | ops.Hadamard(regs.q[0]) 244 | 245 | ZOp = create_operation(circuit, name="ZOp") 246 | 247 | assert ZOp().to_qasm() == "zop" 248 | assert ZOp(Qubit(0)).to_qasm() == "zop q[0]" 249 | 250 | def test_create_operation_no_label(self): 251 | """Test creating an operation without a label.""" 252 | circuit = Circuit(1) 253 | 254 | with circuit.context as regs: 255 | ops.Hadamard(regs.q[0]) 256 | ops.X(regs.q[0]) 257 | ops.Hadamard(regs.q[0]) 258 | 259 | ZOp = create_operation(circuit) 260 | 261 | assert ZOp().to_qasm() == "customoperation" 262 | assert ZOp(Qubit(0)).to_qasm() == "customoperation q[0]" 263 | 264 | 265 | class TestRegisterOpenQASM: 266 | """Unittests for generating OpenQASM 2.0 from registers.""" 267 | 268 | def test_gate_qreg(self): 269 | """Test generating OpenQASM 2.0 for a quantum register.""" 270 | qreg = QuantumRegister() 271 | assert qreg.to_qasm() == "qreg q[0]" 272 | 273 | def test_gate_qreg_with_data(self): 274 | """Test generating OpenQASM 2.0 for a quantum register with data.""" 275 | qreg = QuantumRegister([Qubit("banana"), Qubit("coconut")]) 276 | assert qreg.to_qasm() == "qreg q[2]" 277 | 278 | def test_gate_creg(self): 279 | """Test generating OpenQASM 2.0 for a classical register.""" 280 | qreg = ClassicalRegister() 281 | assert qreg.to_qasm() == "creg c[0]" 282 | 283 | def test_gate_creg_with_data(self): 284 | """Test generating OpenQASM 2.0 for a classical register with data.""" 285 | qreg = ClassicalRegister([Bit("blueberry"), Bit("citrus")]) 286 | assert qreg.to_qasm() == "creg c[2]" 287 | -------------------------------------------------------------------------------- /tests/test_mixedproperty.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import pytest 16 | 17 | from dwave.gate.mixedproperty import mixedproperty 18 | 19 | 20 | class MixedpropertyExample: 21 | """Dummy class to test the ``mixedproperty`` decorator.""" 22 | 23 | _a = "a_class" 24 | _b = "b_class" 25 | 26 | def __init__(self): 27 | self._b = "b_instance" 28 | self._c = "c_instance" 29 | 30 | # can be called on both instance and class and will 31 | # return the same result regardless 32 | @mixedproperty 33 | def a(cls): 34 | return cls._a 35 | 36 | # can be called on both instance and class and will 37 | # return different results (e.g., different defaults) 38 | @mixedproperty 39 | def b(cls, self): 40 | if self: 41 | return self._b 42 | return cls._b 43 | 44 | # can be called on both instance and class and will 45 | # return 'None' if called on class 46 | @mixedproperty(self_required=True) 47 | def c(cls, self): 48 | return self._c 49 | 50 | 51 | @pytest.fixture(scope="function") 52 | def mixedpropertyexample(): 53 | """Returns a instance of the MixedpropertyExample class.""" 54 | return MixedpropertyExample() 55 | 56 | 57 | class TestMixedPropery: 58 | """Unittests for the mixedproperty class/decorator.""" 59 | 60 | def test_with_class(self, mixedpropertyexample): 61 | """Test a mixedproperty with access only to the class.""" 62 | assert mixedpropertyexample.a == "a_class" 63 | assert MixedpropertyExample.a == "a_class" 64 | 65 | def test_with_instance(self, mixedpropertyexample): 66 | """Test a mixedproperty with access to the class and instance.""" 67 | assert mixedpropertyexample.b == "b_instance" 68 | assert MixedpropertyExample.b == "b_class" 69 | 70 | def test_with_self_required(self, mixedpropertyexample): 71 | """Test a mixedproperty with access only to the instance.""" 72 | assert mixedpropertyexample.c == "c_instance" 73 | assert MixedpropertyExample.c is None 74 | 75 | def test_set_mixedproperty_with_class(self, mixedpropertyexample): 76 | """Test that the correct error is raised when setting a mixedproperty with access only to the class.""" 77 | with pytest.raises(AttributeError, match="can't set attribute"): 78 | mixedpropertyexample.a = "apple" 79 | 80 | def test_set_mixedproperty_with_instance(self, mixedpropertyexample): 81 | """Test that the correct error is raised when setting a mixedproperty with access to the class and instance.""" 82 | with pytest.raises(AttributeError, match="can't set attribute"): 83 | mixedpropertyexample.b = "banana" 84 | 85 | def test_set_mixedproperty_with_self_required(self, mixedpropertyexample): 86 | """Test that the correct error is raised when setting a mixedproperty with access only to the instance.""" 87 | with pytest.raises(AttributeError, match="can't set attribute"): 88 | mixedpropertyexample.c = "coconut" 89 | -------------------------------------------------------------------------------- /tests/test_operations/test_base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import math 16 | 17 | import numpy as np 18 | import pytest 19 | 20 | import dwave.gate.operations.operations as ops 21 | from dwave.gate.circuit import Circuit, ParametricCircuit 22 | from dwave.gate.operations.base import ( 23 | ABCLockedAttr, 24 | Barrier, 25 | Measurement, 26 | Operation, 27 | create_operation, 28 | ) 29 | 30 | 31 | class TestLockedMetaclass: 32 | """Unit tests for the ``ABCLockedAttr`` metaclass. 33 | 34 | The list of locked attributes is part of the ``ABCLockedAttr`` metaclass. If adding a locked 35 | attribute there, please update this test to also test that locked attribute. 36 | """ 37 | 38 | def test_locked_attribute(self): 39 | """Test that a locked class attribute cannot be changed.""" 40 | 41 | class DummyLocked(metaclass=ABCLockedAttr): 42 | """Dummy class used to test the ``ABCLockedAttr`` metaclass.""" 43 | 44 | matrix = [[1, 0], [0, 1]] 45 | not_matrix = 42 46 | 47 | assert DummyLocked.not_matrix == 42 48 | DummyLocked.not_matrix = 24 49 | assert DummyLocked.not_matrix == 24 50 | 51 | with pytest.raises(ValueError, match="Cannot set class attribute"): 52 | DummyLocked.matrix = 5 53 | 54 | def test_locked_attribute_subclass(self): 55 | """Test that a locked class attribute cannot be changed in a subclass.""" 56 | 57 | class DummyLocked(metaclass=ABCLockedAttr): 58 | """Dummy class used to test the ``ABCLockedAttr`` metaclass.""" 59 | 60 | matrix = [[1, 0], [0, 1]] 61 | not_matrix = 42 62 | 63 | class DummySub(DummyLocked): 64 | pass 65 | 66 | assert DummySub.not_matrix == 42 67 | DummySub.not_matrix = 24 68 | assert DummySub.not_matrix == 24 69 | 70 | with pytest.raises(ValueError, match="Cannot set class attribute"): 71 | DummySub.matrix = 5 72 | 73 | 74 | class TestMatrixRepr: 75 | """Unit tests for all matrix representations of operations.""" 76 | 77 | @pytest.mark.parametrize( 78 | "op, matrix", 79 | [ 80 | (ops.X, np.array([[0, 1], [1, 0]])), 81 | (ops.Y, np.array([[0.0, -1.0j], [1.0j, 0.0]])), 82 | (ops.Z, np.array([[1, 0], [0, -1]])), 83 | (ops.Hadamard, math.sqrt(2) / 2 * np.array([[1.0, 1.0], [1.0, -1.0]])), 84 | (ops.RX(np.pi / 2), math.sqrt(2) / 2 * np.array([[1, -1j], [-1j, 1]])), 85 | (ops.RY(np.pi / 2), math.sqrt(2) / 2 * np.array([[1, -1], [1, 1]])), 86 | (ops.RZ(np.pi / 2), math.sqrt(2) / 2 * np.array([[1 - 1j, 0], [0, 1 + 1j]])), 87 | (ops.Rotation([np.pi / 2] * 3), math.sqrt(2) / 2 * np.array([[-1j, -1], [1, 1j]])), 88 | (ops.CX, np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])), 89 | (ops.CNOT, np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])), 90 | # (ops.CX(1, 0), np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]])), 91 | (ops.CZ, np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]])), 92 | ( 93 | ops.SWAP, 94 | np.array( 95 | [ 96 | [1.0, 0.0, 0.0, 0.0], 97 | [0.0, 0.0, 1.0, 0.0], 98 | [0.0, 1.0, 0.0, 0.0], 99 | [0.0, 0.0, 0.0, 1.0], 100 | ] 101 | ), 102 | ), 103 | ( 104 | ops.CSWAP, 105 | np.array( 106 | [ 107 | [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 108 | [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 109 | [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], 110 | [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], 111 | [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], 112 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], 113 | [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], 114 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], 115 | ] 116 | ), 117 | ), 118 | ( 119 | ops.CCNOT, 120 | np.array( 121 | [ 122 | [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 123 | [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 124 | [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], 125 | [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], 126 | [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], 127 | [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], 128 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0], 129 | [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], 130 | ] 131 | ), 132 | ), 133 | ( 134 | ops.CRX(np.pi / 2), 135 | np.array( 136 | [ 137 | [1.0, 0.0, 0.0, 0.0], 138 | [0.0, 1.0, 0.0, 0.0], 139 | [0.0, 0.0, math.sqrt(2) / 2, math.sqrt(2) / 2 * -1j], 140 | [0.0, 0.0, math.sqrt(2) / 2 * -1j, math.sqrt(2) / 2], 141 | ] 142 | ), 143 | ), 144 | ( 145 | ops.CRY(np.pi / 2), 146 | np.array( 147 | [ 148 | [1.0, 0.0, 0.0, 0.0], 149 | [0.0, 1.0, 0.0, 0.0], 150 | [0.0, 0.0, math.sqrt(2) / 2 * 1.0, -math.sqrt(2) / 2], 151 | [0.0, 0.0, math.sqrt(2) / 2 * 1.0, math.sqrt(2) / 2 * 1.0], 152 | ] 153 | ), 154 | ), 155 | ( 156 | ops.CRZ(np.pi / 2), 157 | np.array( 158 | [ 159 | [1.0, 0.0, 0.0, 0.0], 160 | [0.0, 1.0, 0.0, 0.0], 161 | [0.0, 0.0, math.sqrt(2) / 2 * (1 - 1j), 0.0], 162 | [0.0, 0.0, 0.0, math.sqrt(2) / 2 * (1 + 1j)], 163 | ] 164 | ), 165 | ), 166 | ( 167 | ops.CRotation([np.pi / 2] * 3), 168 | np.array( 169 | [ 170 | [1.0, 0.0, 0.0, 0.0], 171 | [0.0, 1.0, 0.0, 0.0], 172 | [0.0, 0.0, math.sqrt(2) / 2 * -1j, math.sqrt(2) / 2 * -1.0], 173 | [0.0, 0.0, math.sqrt(2) / 2 * 1.0, math.sqrt(2) / 2 * 1j], 174 | ] 175 | ), 176 | ), 177 | ], 178 | ) 179 | def test_matrix_repr(self, op, matrix): 180 | """Test that matrix representations are correct.""" 181 | assert np.allclose(op.matrix, matrix) 182 | 183 | # if matrix is accessed as a classproperty, then test it on an instance as well 184 | if not isinstance(op, Operation): 185 | assert np.allclose(op().matrix, matrix) 186 | 187 | @pytest.mark.parametrize( 188 | "op, qubits, matrix", 189 | [ 190 | (ops.CX, [0, 1], np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])), 191 | (ops.CNOT, [0, 1], np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])), 192 | (ops.CX, [1, 0], np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]])), 193 | (ops.CZ, [0, 1], np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]])), 194 | ], 195 | ) 196 | def test_controlled_matrix_repr(self, op, qubits, matrix, two_qubit_circuit): 197 | """Test that controlled matrix representations are correct.""" 198 | two_qubit_circuit.unlock() 199 | with two_qubit_circuit.context as regs: 200 | assert np.allclose(op(*[regs.q[i] for i in qubits]).matrix, matrix) 201 | 202 | 203 | class TestMeasurement: 204 | """Unit tests for the ``Measurement`` class.""" 205 | 206 | def test_initialize_measurement(self): 207 | """Test initializing a measurement operation.""" 208 | m = Measurement() 209 | assert repr(m) == "" 210 | 211 | def test_pipe_single_measurement(self, quantum_register, classical_register): 212 | """Test measuring a qubit and 'piping' the value to a classical register.""" 213 | qubit = quantum_register[0] 214 | bit = classical_register[0] 215 | m = Measurement(qubits=qubit) | bit 216 | assert m.__repr__() == f"" 217 | 218 | assert m.bits == (bit,) 219 | 220 | def test_pipe_measurements(self, quantum_register, classical_register): 221 | """Test measuring several qubits and 'piping' the values to a classical register.""" 222 | qubits = quantum_register 223 | bits = classical_register 224 | m = Measurement(qubits=qubits) | bits 225 | assert m.__repr__() == f"" 226 | 227 | assert m.bits == tuple(bits) 228 | 229 | def test_pipe_measurement_to_seq(self, quantum_register, classical_register): 230 | """Test measuring several qubits and 'piping' the values to a sequence of bits.""" 231 | qubits = quantum_register 232 | bits = tuple(classical_register) 233 | m = Measurement(qubits=qubits) | bits 234 | assert m.__repr__() == f"" 235 | 236 | assert m.bits == tuple(bits) 237 | 238 | def test_pipe_measurements_to_large_register(self, quantum_register, classical_register): 239 | """Test measuring several qubits and 'piping' the values to a larger classical register.""" 240 | qubits = quantum_register[:2] 241 | bits = classical_register 242 | m = Measurement(qubits=qubits) | bits 243 | assert m.__repr__() == f"" 244 | 245 | assert m.bits == tuple(bits[:2]) 246 | 247 | def test_pipe_measurements_to_small_register(self, quantum_register, classical_register): 248 | """Test measuring several qubits and 'piping' the values to a smaller classical register.""" 249 | qubits = quantum_register 250 | bits = classical_register[:2] 251 | with pytest.raises(ValueError, match=r"Measuring 4 qubit\(s\), passed to only 2 bits."): 252 | m = Measurement(qubits=qubits) | bits 253 | 254 | 255 | @pytest.mark.xfail(reason="Barriers are not implemented yet.") 256 | class TestBarrier: 257 | """Unit tests for the ``Barrier`` class.""" 258 | 259 | 260 | class TestCreateOperation: 261 | """Unit tests for the ``create_operation`` function.""" 262 | 263 | def test_parametric(self): 264 | """Test creating an operation out of a parametric circuit.""" 265 | circuit = ParametricCircuit(1) 266 | 267 | with circuit.context as regs: 268 | ops.RZ(regs.p[0], regs.q[0]) 269 | ops.RY(regs.p[1], regs.q[0]) 270 | ops.RZ(regs.p[2], regs.q[0]) 271 | 272 | RotOp = create_operation(circuit, name="Rot") 273 | params = [0.23, 0.34, 0.45] 274 | rot_op = RotOp(params) 275 | 276 | assert RotOp.matrix is None 277 | assert RotOp.name == "Rot" 278 | 279 | assert np.allclose(rot_op.matrix, ops.Rotation(params).matrix) 280 | assert rot_op.name == f"Rot({params})" 281 | 282 | def test_non_parametric(self): 283 | """Test creating an operation out of a non-parametric circuit.""" 284 | circuit = Circuit(1) 285 | 286 | with circuit.context as regs: 287 | ops.Hadamard(regs.q[0]) 288 | ops.X(regs.q[0]) 289 | ops.Hadamard(regs.q[0]) 290 | 291 | ZOp = create_operation(circuit, name="Z") 292 | 293 | assert np.allclose(ZOp.matrix, ops.Z.matrix) 294 | assert ZOp.name == "Z" 295 | 296 | def test_no_label(self): 297 | """Test creating an operation without a label.""" 298 | circuit = Circuit(1) 299 | 300 | with circuit.context as regs: 301 | ops.Hadamard(regs.q[0]) 302 | ops.X(regs.q[0]) 303 | ops.Hadamard(regs.q[0]) 304 | 305 | ZOp = create_operation(circuit) 306 | 307 | assert ZOp.name == "CustomOperation" 308 | 309 | def test_parametric_mix(self): 310 | """Test creating an operation out of a parametric circuit with some hard-coded 311 | parameters.""" 312 | circuit = ParametricCircuit(1) 313 | 314 | with circuit.context as regs: 315 | ops.RZ(0.1, regs.q[0]) 316 | ops.RY(regs.p[0], regs.q[0]) 317 | ops.RZ(0.3, regs.q[0]) 318 | ops.X(regs.q[0]) # cover testing non-parametric ops in parametric circuits 319 | ops.X(regs.q[0]) # negate the previous X-gate to compare with Rotation-gate 320 | 321 | RotOp = create_operation(circuit, name="Rot") 322 | params = [0.2] 323 | rot_op = RotOp(params) 324 | 325 | assert RotOp.matrix is None 326 | assert RotOp.name == "Rot" 327 | 328 | assert np.allclose(rot_op.matrix, ops.Rotation([0.1, 0.2, 0.3]).matrix) 329 | assert rot_op.name == "Rot([0.2])" 330 | -------------------------------------------------------------------------------- /tests/test_primitives.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import pytest 16 | 17 | from dwave.gate.primitives import Bit, Qubit, Variable 18 | 19 | 20 | class TestQubit: 21 | """Unit tests for the ``Qubit`` class.""" 22 | 23 | def test_initialize(self): 24 | """Test initializing a qubit.""" 25 | label = "ananas" 26 | qubit = Qubit(label) 27 | 28 | assert qubit.label == label 29 | 30 | def test_set_label(self): 31 | """Test set qubit label.""" 32 | qubit = Qubit("ananas") 33 | assert qubit.label == "ananas" 34 | 35 | qubit.label = "banana" 36 | assert qubit.label == "banana" 37 | 38 | def test_equality(self): 39 | "Test equality of two qubits with the same label." 40 | label = "ananas" 41 | bit_0 = Qubit(label) 42 | bit_1 = Qubit(label) 43 | 44 | assert bit_0 != bit_1 45 | assert bit_0 is not bit_1 46 | 47 | def test_repr(self): 48 | "Test the representation of a qubit." 49 | qubit = Qubit("ananas") 50 | assert qubit.__repr__() == f"" 51 | 52 | 53 | class TestBit: 54 | """Unit tests for the ``Bit`` class.""" 55 | 56 | def test_initialize(self): 57 | """Test initializing a bit.""" 58 | label = "ananas" 59 | bit = Bit(label) 60 | 61 | assert bit.label == label 62 | 63 | def test_set_label(self): 64 | """Test set bit label.""" 65 | bit = Bit("ananas") 66 | assert bit.label == "ananas" 67 | 68 | bit.label = "banana" 69 | assert bit.label == "banana" 70 | 71 | def test_equality(self): 72 | "Test equality of two bits with the same label." 73 | label = "ananas" 74 | bit_0 = Bit(label) 75 | bit_1 = Bit(label) 76 | 77 | assert bit_0 != bit_1 78 | assert bit_0 is not bit_1 79 | 80 | def test_repr(self): 81 | "Test the representation of a bit." 82 | bit = Bit("ananas") 83 | assert bit.__repr__() == f"" 84 | 85 | @pytest.mark.parametrize("value", [1, 0, "1", True, 42, (1, 2, 3)]) 86 | def test_set_value(self, value): 87 | """Test setting a value for the bit.""" 88 | bit = Bit("banana") 89 | bit.set(value) 90 | 91 | assert bit == int(bool(value)) 92 | assert bit.__repr__() == f"" 93 | 94 | bit.reset() 95 | 96 | assert bit != 3.14 97 | 98 | def test_force_set_value(self): 99 | """Test forcing a value for the bit.""" 100 | bit = Bit("banana") 101 | bit.set(1) 102 | assert bit.value == 1 103 | 104 | with pytest.raises(ValueError, match="Value already set"): 105 | bit.set(0) 106 | 107 | bit.set(0, force=True) 108 | assert bit.value == 0 109 | 110 | @pytest.mark.parametrize("value", [1, 0, "1", True]) 111 | def test_bool(self, value): 112 | """Test the boolean represention of the bit.""" 113 | bit = Bit("coconut") 114 | bit.set(value) 115 | 116 | assert bool(bit) == bool(value) 117 | 118 | 119 | class TestVariable: 120 | """Unit tests for the ``Variable`` class.""" 121 | 122 | def test_initialize(self): 123 | """Test initializing a variable.""" 124 | name = "ananas" 125 | var = Variable(name) 126 | 127 | assert var.name == name 128 | 129 | def test_hash(self): 130 | "Test that two equal variables have the same hash." 131 | name = "ananas" 132 | var_0 = Variable(name) 133 | var_1 = Variable(name) 134 | 135 | assert var_0 == var_1 136 | assert hash(var_0) == hash(var_1) 137 | 138 | def test_equality(self): 139 | "Test equality of two variables with the same name." 140 | name = "ananas" 141 | var_0 = Variable(name) 142 | var_1 = Variable(name) 143 | 144 | assert var_0 == var_1 145 | assert var_0 is not var_1 146 | 147 | assert var_0 != name 148 | 149 | def test_repr(self): 150 | "Test the representation of a variable." 151 | var = Variable("ananas") 152 | assert var.__repr__() == "{ananas}" 153 | 154 | def test_set_value(self): 155 | """Test setting a value for the variable.""" 156 | var = Variable("banana") 157 | assert var.__repr__() == "{banana}" 158 | 159 | var.set(3.14) 160 | 161 | assert var == 3.14 162 | assert var.__repr__() == "3.14" 163 | 164 | var.reset() 165 | 166 | assert var != 3.14 167 | assert var.__repr__() == "{banana}" 168 | -------------------------------------------------------------------------------- /tests/test_qir/test_instructions.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import itertools 16 | 17 | import pytest 18 | 19 | pyqir = pytest.importorskip("pyqir") 20 | # QIR submodule imports must postcede PyQIR importorskip 21 | from dwave.gate.qir.compiler import BaseModule # noqa: E402 22 | from dwave.gate.qir.instructions import InstrType, Instruction # noqa: E402 23 | 24 | 25 | class TestInstruction: 26 | """Tests for the ``Instruction`` class.""" 27 | 28 | @pytest.mark.parametrize( 29 | "type_name", ["BASE", "EXTERNAL", "DECOMPOSE", "INVALID", "SKIP", "MEASUREMENT", "OUTPUT"] 30 | ) 31 | def test_instruction(self, type_name, dummy_function): 32 | """Test the ``Instruction`` initializer.""" 33 | name = "pomelo" 34 | args = [1.2, 2.3] 35 | type_ = getattr(InstrType, type_name) 36 | instr = Instruction(type_, name, args, external=dummy_function) 37 | 38 | assert instr.name == name 39 | assert instr.type is type_ 40 | assert instr.args == args 41 | 42 | @pytest.mark.parametrize("args", [[1 + 2j], [0.1, "hello"]]) 43 | def test_args_error(self, args): 44 | """Test that the correct exception is raised when passing invalid argument types.""" 45 | 46 | with pytest.raises(TypeError, match="Incorrect type"): 47 | Instruction(InstrType.BASE, "pomelo", args) 48 | 49 | @pytest.mark.parametrize("type_name", ["BASE", "MEASUREMENT"]) 50 | def test_excecute_instruction(self, type_name, mocker): 51 | """Test excecuting instructions.""" 52 | type_ = getattr(InstrType, type_name) 53 | instr = Instruction(type_, "pomelo", []) 54 | 55 | mod = BaseModule("QIR_module", 2, 1) 56 | 57 | def _assert_qis_called(): 58 | assert instr.type in (InstrType.BASE, InstrType.MEASUREMENT) 59 | 60 | module = Instruction.__module__ 61 | qis_mock = mocker.patch(f"{module}.Instruction._execute_qis") 62 | qis_mock.side_effect = lambda _: _assert_qis_called() 63 | 64 | instr.execute(mod.builder) 65 | 66 | qis_mock.assert_called_once() 67 | 68 | def test_excecute_instruction_ext(self, mocker, dummy_function): 69 | """Test excecuting instructions.""" 70 | instr = Instruction(InstrType.EXTERNAL, "pomelo", [], external=dummy_function) 71 | 72 | mod = BaseModule("QIR_module", 2, 1) 73 | 74 | def _assert_ext_called(): 75 | assert instr.type is InstrType.EXTERNAL 76 | 77 | module = Instruction.__module__ 78 | ext_mock = mocker.patch(f"{module}.Instruction._execute_external") 79 | ext_mock.side_effect = lambda _: _assert_ext_called() 80 | 81 | instr.execute(mod.builder) 82 | 83 | ext_mock.assert_called_once() 84 | 85 | def test_instruction_no_external( 86 | self, 87 | ): 88 | """Test external instruction with no external function.""" 89 | 90 | with pytest.raises( 91 | ValueError, match="Instruction with type 'external' missing external function." 92 | ): 93 | _ = Instruction(InstrType.EXTERNAL, "pomelo", []) 94 | 95 | @pytest.mark.parametrize("type_name", ["DECOMPOSE", "INVALID", "SKIP", "OUTPUT"]) 96 | def test_excecute_instruction_error(self, type_name, monkeypatch): 97 | """Assert that the correct exception is raised when attempting 98 | to excecute a non-excecutable instructions.""" 99 | args = [1.2, 2.3] 100 | type_ = getattr(InstrType, type_name) 101 | instr = Instruction(type_, "pomelo", args) 102 | 103 | mod = BaseModule("QIR_module", 2, 1) 104 | 105 | with pytest.raises(TypeError, match="Cannot execute instruction"): 106 | instr.execute(mod.builder) 107 | 108 | def test_instruction_equality(self, dummy_function): 109 | """Test equality between ``Instruction`` objects.""" 110 | 111 | instr_0 = Instruction(InstrType.BASE, "pomelo", [1.2, 2.3]) 112 | instr_1 = Instruction(InstrType.EXTERNAL, "pomelo", [1.2, 2.3], external=dummy_function) 113 | instr_2 = Instruction(InstrType.BASE, "grapefruit", [1.2, 2.3]) 114 | instr_3 = Instruction(InstrType.BASE, "pomelo", [9, 8, 7, 6]) 115 | 116 | for i0, i1 in itertools.combinations([instr_0, instr_1, instr_2, instr_3], r=2): 117 | assert i0 != i1 118 | 119 | instr_0_dup = Instruction(InstrType.BASE, "pomelo", [1.2, 2.3]) 120 | instr_3_dup = Instruction(InstrType.BASE, "pomelo", [9.0, 8.0, 7.0, 6.0]) 121 | 122 | assert instr_0 == instr_0_dup 123 | assert instr_3 == instr_3_dup 124 | -------------------------------------------------------------------------------- /tests/test_qir/test_load.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import inspect 16 | 17 | import pytest 18 | 19 | from dwave.gate import operations as ops 20 | from dwave.gate.circuit import Circuit 21 | from dwave.gate.operations.base import Measurement, ParametricOperation 22 | 23 | pyqir = pytest.importorskip("pyqir") 24 | # QIR submodule imports must postcede PyQIR importorskip 25 | from dwave.gate.qir.loader import load_qir_string # noqa: E402 26 | 27 | 28 | def assert_equal(circuit_0, circuit_1): 29 | """Checks two circuits for equality.""" 30 | assert circuit_0.num_qubits == circuit_1.num_qubits 31 | assert circuit_0.num_bits == circuit_1.num_bits 32 | 33 | # check registers (but wait with values, since the qubits/bits will differ) 34 | assert circuit_0.qregisters.keys() == circuit_1.qregisters.keys() 35 | assert circuit_0.cregisters.keys() == circuit_1.cregisters.keys() 36 | 37 | # check qubit/bit labels (not the qubits/bits themselves since they will differ) 38 | qb_labels_0 = [qb.label for qb in circuit_0.qubits] 39 | qb_labels_1 = [qb.label for qb in circuit_1.qubits] 40 | assert qb_labels_0 == qb_labels_1 41 | 42 | mb_labels_0 = [mb.label for mb in circuit_0.bits] 43 | mb_labels_1 = [mb.label for mb in circuit_1.bits] 44 | assert mb_labels_0 == mb_labels_1 45 | 46 | # check parametricity of circuits 47 | assert circuit_0.parametric == circuit_1.parametric 48 | assert circuit_0.num_parameters == circuit_1.num_parameters 49 | 50 | # check number of operations 51 | assert len(circuit_0.circuit) == len(circuit_1.circuit) 52 | 53 | # check all operations; their names, qubits, bits and parameters (if required) 54 | for op_0, op_1 in zip(circuit_0.circuit, circuit_1.circuit): 55 | assert op_0.name == op_1.name 56 | assert type(op_0) == type(op_1) 57 | 58 | qb_labels_0 = [qb.label for qb in op_0.qubits] 59 | qb_labels_1 = [qb.label for qb in op_1.qubits] 60 | assert qb_labels_0 == qb_labels_1 61 | 62 | if isinstance(op_0, Measurement): 63 | mb_labels_0 = [mb.label for mb in op_0.bits] 64 | mb_labels_1 = [mb.label for mb in op_1.bits] 65 | assert mb_labels_0 == mb_labels_1 66 | 67 | if isinstance(op_0, ParametricOperation): 68 | assert op_0.parameters == op_1.parameters 69 | 70 | 71 | class TestLoader: 72 | """Tests for loading QIR into a circuit.""" 73 | 74 | def test_load_qir_string(self): 75 | """Test the ``load_qir_string`` function.""" 76 | 77 | qir_string = inspect.cleandoc( 78 | r""" 79 | ; ModuleID = 'Circuit' 80 | source_filename = "Circuit" 81 | 82 | %Qubit = type opaque 83 | %Result = type opaque 84 | 85 | define void @main() #0 { 86 | entry: 87 | call void @__quantum__rt__initialize(i8* null) 88 | br label %body 89 | 90 | body: ; preds = %entry 91 | call void @__quantum__qis__x__body(%Qubit* null) 92 | call void @__quantum__qis__ry__body(double 4.200000e+00, %Qubit* inttoptr (i64 2 to %Qubit*)) 93 | call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 2 to %Qubit*)) 94 | br label %measurements 95 | 96 | measurements: ; preds = %body 97 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 98 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 99 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) 100 | br label %output 101 | 102 | output: ; preds = %measurements 103 | call void @__quantum__rt__result_record_output(%Result* null, i8* null) 104 | call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) 105 | call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null) 106 | ret void 107 | } 108 | 109 | declare void @__quantum__rt__initialize(i8*) 110 | 111 | declare void @__quantum__qis__x__body(%Qubit*) 112 | 113 | declare void @__quantum__qis__ry__body(double, %Qubit*) 114 | 115 | declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*) 116 | 117 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1 118 | 119 | declare void @__quantum__rt__result_record_output(%Result*, i8*) 120 | 121 | attributes #0 = { "entry_point" "num_required_qubits"="3" "num_required_results"="3" "output_labeling_schema" "qir_profiles"="custom" } 122 | attributes #1 = { "irreversible" } 123 | 124 | !llvm.module.flags = !{!0, !1, !2, !3} 125 | 126 | !0 = !{i32 1, !"qir_major_version", i32 1} 127 | !1 = !{i32 7, !"qir_minor_version", i32 0} 128 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 129 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 130 | """ 131 | ) 132 | 133 | circuit = load_qir_string(qir_string) 134 | 135 | assert circuit.num_qubits == 3 136 | assert circuit.num_bits == 3 137 | assert len(circuit.circuit) == 4 138 | 139 | assert circuit.circuit[0].name == "X" 140 | assert [qb.label for qb in circuit.circuit[0].qubits] == ["0"] 141 | 142 | assert circuit.circuit[1].name == "RY([4.2])" 143 | assert [qb.label for qb in circuit.circuit[1].qubits] == ["2"] 144 | assert circuit.circuit[1].parameters == [4.2] 145 | 146 | assert circuit.circuit[2].name == "CX" 147 | assert [qb.label for qb in circuit.circuit[2].qubits] == ["1", "2"] 148 | 149 | assert circuit.circuit[3].name == "Measurement" 150 | assert [mb.label for mb in circuit.circuit[3].bits] == ["0", "1", "2"] 151 | assert [qb.label for qb in circuit.circuit[3].qubits] == ["0", "1", "2"] 152 | 153 | @pytest.mark.parametrize("bitcode", [True, False]) 154 | def test_to_from_qir(self, bitcode): 155 | """Test compiling and loading QIR works.""" 156 | circuit = Circuit(3, 3) 157 | 158 | with circuit.context as reg: 159 | ops.X(reg.q[0]) 160 | ops.CNOT(reg.q[2], reg.q[0]) 161 | ops.RY(3.45, reg.q[1]) 162 | ops.Measurement(reg.q) | reg.c 163 | 164 | qir = circuit.to_qir(bitcode=bitcode) 165 | circuit_from_qir = Circuit.from_qir(qir, bitcode=bitcode) 166 | 167 | assert_equal(circuit, circuit_from_qir) 168 | 169 | def test_invalid_qis_operation(self): 170 | """Test exception when using non-existent QIS operation.""" 171 | 172 | qir_string = inspect.cleandoc( 173 | r""" 174 | ; ModuleID = 'Citrus' 175 | source_filename = "Citrus" 176 | 177 | %Qubit = type opaque 178 | 179 | define void @main() { 180 | entry: 181 | call void @__quantum__rt__initialize(i8* null) 182 | call void @__quantum__qis__xavier__body(%Qubit* null) 183 | ret void 184 | } 185 | 186 | declare void @__quantum__rt__initialize(i8*) 187 | declare void @__quantum__qis__xavier__body(%Qubit*) 188 | """ 189 | ) 190 | with pytest.raises(TypeError, match="not found in valid QIS operations"): 191 | _ = Circuit.from_qir(qir_string) 192 | 193 | def test_load_QIR_into_circuit(self): 194 | """Test loading QIR into an existing circuit.""" 195 | qir_string = inspect.cleandoc( 196 | r""" 197 | ; ModuleID = 'Citrus' 198 | source_filename = "Citrus" 199 | 200 | %Qubit = type opaque 201 | 202 | define void @main() { 203 | entry: 204 | call void @__quantum__rt__initialize(i8* null) 205 | call void @__quantum__qis__y__body(%Qubit* null) 206 | ret void 207 | } 208 | 209 | declare void @__quantum__rt__initialize(i8*) 210 | declare void @__quantum__qis__y__body(%Qubit*) 211 | """ 212 | ) 213 | circuit = Circuit(2) 214 | circuit.append(ops.X(circuit.qubits[0])) 215 | 216 | assert len(circuit.circuit) == 1 217 | 218 | circuit = load_qir_string(qir_string, circuit=circuit) 219 | 220 | assert circuit.num_qubits == 2 221 | assert circuit.num_bits == 0 222 | assert len(circuit.circuit) == 2 223 | 224 | circuit.unlock() 225 | circuit.append(ops.Z(circuit.qubits[0])) 226 | 227 | assert [op.name for op in circuit.circuit] == ["X", "Y", "Z"] 228 | 229 | def test_early_ret(self): 230 | """Test setting a return in an earlier block.""" 231 | qir_string = inspect.cleandoc( 232 | r""" 233 | ; ModuleID = 'Citrus' 234 | source_filename = "Citrus" 235 | 236 | %Qubit = type opaque 237 | 238 | define void @main() { 239 | entry: 240 | call void @__quantum__rt__initialize(i8* null) 241 | call void @__quantum__qis__x__body(%Qubit* null) 242 | ret void 243 | 244 | body: ; preds = %entry 245 | call void @__quantum__qis__y__body(%Qubit* null) 246 | ret void 247 | } 248 | 249 | declare void @__quantum__rt__initialize(i8*) 250 | declare void @__quantum__qis__x__body(%Qubit*) 251 | declare void @__quantum__qis__y__body(%Qubit*) 252 | """ 253 | ) 254 | circuit = Circuit(2) 255 | 256 | circuit = load_qir_string(qir_string, circuit=circuit) 257 | 258 | assert len(circuit.circuit) == 1 259 | assert [op.name for op in circuit.circuit] == ["X"] 260 | -------------------------------------------------------------------------------- /tests/test_registers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import pytest 16 | 17 | from dwave.gate.primitives import Bit, Qubit, Variable 18 | from dwave.gate.registers.registers import ( 19 | ClassicalRegister, 20 | QuantumRegister, 21 | RegisterError, 22 | SelfIncrementingRegister, 23 | ) 24 | 25 | 26 | class TestQuantumRegister: 27 | """Unit tests for the quantum register class.""" 28 | 29 | def test_empty_register(self): 30 | """Test initializing an empty data register.""" 31 | reg = QuantumRegister() 32 | 33 | assert reg.data == [] 34 | assert len(reg) == 0 35 | 36 | def test_register_with_data(self): 37 | """Test initializing an empty data register.""" 38 | data = [Qubit(1), Qubit(2), Qubit(3), Qubit(42)] 39 | reg = QuantumRegister(data=data) 40 | 41 | assert reg.data == data 42 | assert len(reg) == 4 43 | 44 | def test_register_iteration(self): 45 | """Test iterating over a register.""" 46 | data = [Qubit(1), Qubit(21), Qubit(32)] 47 | reg = QuantumRegister(data=data) 48 | 49 | for i, d in enumerate(reg): 50 | assert d == data[i] 51 | 52 | def test_index(self): 53 | """Test getting the index of an item in a register.""" 54 | data = [Qubit(1), Qubit(13), Qubit(42), Qubit(54)] 55 | reg = QuantumRegister(data=data) 56 | 57 | assert reg.index(data[0]) == data.index(data[0]) 58 | 59 | def test_register_indexing(self): 60 | """Test getting an item at a specific index in a register.""" 61 | data = [Qubit(1), Qubit(13), Qubit(42), Qubit(54)] 62 | reg = QuantumRegister(data=data) 63 | 64 | # don't use enumerate so as to _only_ test indexing (and not iteration) 65 | for i in range(len(reg)): 66 | assert reg[i] == data[i] 67 | 68 | def test_register_indexing_out_of_range(self): 69 | """Test getting a item at a index out-of-range in a register.""" 70 | reg = QuantumRegister(data=[Qubit(1), Qubit(13), Qubit(42), Qubit(54)]) 71 | 72 | with pytest.raises(IndexError, match="index out of range"): 73 | reg[42] 74 | 75 | def test_contains(self): 76 | """Test checking if an item is in a register.""" 77 | data = [Qubit(1), Qubit(13), Qubit(42), Qubit(54)] 78 | reg = QuantumRegister(data) 79 | 80 | assert data[1] in reg 81 | assert 657 not in reg 82 | assert Qubit(1) not in reg 83 | 84 | def test_repr(self): 85 | """Test the representation of a register.""" 86 | data = [Qubit(1), Qubit(13), Qubit(42), Qubit(54)] 87 | reg = QuantumRegister(data) 88 | 89 | assert reg.__repr__() == f"" 90 | 91 | def test_str(self): 92 | """Test the string of a register.""" 93 | data = [Qubit(1), Qubit(13), Qubit(42), Qubit(54)] 94 | reg = QuantumRegister(data) 95 | 96 | assert str(reg) == f"QuantumRegister({data})" 97 | 98 | def test_add(self): 99 | """Test adding qubits to the register.""" 100 | data = [Qubit(0), Qubit(1)] 101 | reg = QuantumRegister(data=data) 102 | assert reg.data == data 103 | 104 | qubits = [Qubit(3), Qubit("pineapple")] 105 | reg.add(qubits) 106 | assert reg.data == data + qubits 107 | 108 | def test_add_duplicate_data(self): 109 | """Test adding qubits to the register using existing labels.""" 110 | data = [Qubit("banana"), Qubit("mango")] 111 | reg = QuantumRegister(data) 112 | assert reg.data == data 113 | 114 | with pytest.raises(ValueError, match="already in register"): 115 | reg.add(data[0]) 116 | 117 | def test_freeze(self): 118 | """Test freezing the register.""" 119 | reg = QuantumRegister(data=[Qubit(0), Qubit(1)]) 120 | reg.freeze() 121 | 122 | assert reg.frozen 123 | 124 | with pytest.raises( 125 | RegisterError, match="Register is frozen and no more data can be added." 126 | ): 127 | reg.add("abc") 128 | 129 | 130 | class TestClassicalRegister: 131 | """Unit tests for the classical register class.""" 132 | 133 | def test_empty_register(self): 134 | """Test initializing an empty data register.""" 135 | reg = ClassicalRegister() 136 | 137 | assert reg.data == [] 138 | assert len(reg) == 0 139 | 140 | def test_register_with_data(self): 141 | """Test initializing an empty data register.""" 142 | data = [Bit(1), Bit(2), Bit(3), Bit(42)] 143 | reg = ClassicalRegister(data) 144 | 145 | assert reg.data == data 146 | assert len(reg) == 4 147 | 148 | def test_register_iteration(self): 149 | """Test iterating over a register.""" 150 | data = [Bit(1), Bit(21), Bit(32)] 151 | reg = ClassicalRegister(data=data) 152 | 153 | for i, d in enumerate(reg): 154 | assert d == data[i] 155 | 156 | def test_index(self): 157 | """Test getting the index of an item in a register.""" 158 | data = [Bit(1), Bit(13), Bit(42), Bit(54)] 159 | reg = ClassicalRegister(data=data) 160 | 161 | assert reg.index(data[1]) == data.index(data[1]) 162 | 163 | def test_register_indexing(self): 164 | """Test getting an item at a specific index in a register.""" 165 | data = [Bit(1), Bit(13), Bit(42), Bit(54)] 166 | reg = ClassicalRegister(data=data) 167 | 168 | # don't use enumerate so as to _only_ test indexing (and not iteration) 169 | for i in range(len(reg)): 170 | assert reg[i] == data[i] 171 | 172 | def test_repr(self): 173 | """Test the representation of a register.""" 174 | data = [Bit(1), Bit(13), Bit(42), Bit(54)] 175 | reg = ClassicalRegister(data) 176 | 177 | assert reg.__repr__() == f"" 178 | 179 | def test_str(self): 180 | """Test the string of a register.""" 181 | data = [Bit(1), Bit(13), Bit(42), Bit(54)] 182 | reg = ClassicalRegister(data) 183 | 184 | assert str(reg) == f"ClassicalRegister({data})" 185 | 186 | def test_add(self): 187 | """Test adding bits to the register.""" 188 | data = [Bit(1), Bit(0)] 189 | reg = ClassicalRegister(data) 190 | assert reg.data == data 191 | 192 | bits = [Bit(42), Bit(24)] 193 | reg.add(bits) 194 | assert reg.data == data + bits 195 | 196 | def test_add_duplicate_data(self): 197 | """Test adding bits to the register using existing labels.""" 198 | data = [Bit("banana"), Bit("mango")] 199 | reg = ClassicalRegister(data) 200 | assert reg.data == data 201 | 202 | with pytest.raises(ValueError, match="already in register"): 203 | reg.add(data[0]) 204 | 205 | def test_freeze(self): 206 | """Test freezing the register.""" 207 | data = [Bit(1), Bit(0)] 208 | reg = ClassicalRegister(data) 209 | reg.freeze() 210 | 211 | assert reg.frozen 212 | 213 | with pytest.raises( 214 | RegisterError, match="Register is frozen and no more data can be added." 215 | ): 216 | reg.add(Bit("123")) 217 | 218 | 219 | class TestSelfIncrementingRegister: 220 | """Unit tests for the ``SelfIncrementingRegister`` class.""" 221 | 222 | def test_empty_register(self): 223 | """Test initializing an empty register.""" 224 | reg = SelfIncrementingRegister() 225 | 226 | assert reg.data == [] 227 | assert len(reg) == 0 228 | 229 | def test_register_with_data(self): 230 | """Test initializing an empty data register.""" 231 | data = [Variable("a"), Variable("b"), Variable("c"), Variable("d")] 232 | reg = SelfIncrementingRegister(data) 233 | 234 | assert reg.data == data 235 | assert len(reg) == 4 236 | 237 | def test_register_iteration(self): 238 | """Test iterating over a register.""" 239 | data = [Variable("a"), Variable("b"), Variable("c"), Variable("d")] 240 | reg = SelfIncrementingRegister(data) 241 | 242 | for i, d in enumerate(reg): 243 | assert d == data[i] 244 | 245 | def test_index(self): 246 | """Test getting the index of an item in a register.""" 247 | data = [Variable("a"), Variable("b"), Variable("c"), Variable("d")] 248 | reg = SelfIncrementingRegister(data) 249 | 250 | assert reg.index(data[2]) == data.index(data[2]) 251 | 252 | def test_register_indexing(self): 253 | """Test getting an item at a specific index in a register.""" 254 | data = [Variable("a"), Variable("b"), Variable("c"), Variable("d")] 255 | reg = SelfIncrementingRegister(data) 256 | 257 | # don't use enumerate so as to _only_ test indexing (and not iteration) 258 | for i in range(len(reg)): 259 | assert reg[i] == data[i] 260 | 261 | def test_indexing_outside_scope(self): 262 | """Test getting an item at an index outside the scope of the register.""" 263 | data = [Variable("a"), Variable("b")] 264 | reg = SelfIncrementingRegister(data) 265 | 266 | assert reg[4] == Variable("4") 267 | assert reg.data == data + [Variable("2"), Variable("3"), Variable("4")] 268 | 269 | def test_repr(self): 270 | """Test the representation of a register.""" 271 | data = [Variable("a"), Variable("b"), Variable("c"), Variable("d")] 272 | reg = SelfIncrementingRegister(data) 273 | 274 | assert reg.__repr__() == f"" 275 | 276 | def test_str(self): 277 | """Test the string of a register.""" 278 | data = [Variable("a"), Variable("b"), Variable("c"), Variable("d")] 279 | reg = SelfIncrementingRegister(data) 280 | 281 | assert str(reg) == f"SelfIncrementingRegister({data})" 282 | 283 | def test_add(self): 284 | """Test adding variables to the register.""" 285 | data = [Variable("a"), Variable("b")] 286 | reg = SelfIncrementingRegister(data) 287 | assert reg.data == data 288 | 289 | vars = [Variable("c"), Variable("d")] 290 | reg.add(vars) 291 | assert reg.data == data + vars 292 | 293 | def test_add_duplicate_data(self): 294 | """Test adding bits to the register using existing labels.""" 295 | data = [Variable("banana"), Variable("mango")] 296 | reg = SelfIncrementingRegister(data) 297 | assert reg.data == data 298 | 299 | # check that new variables using the same same are equal 300 | assert reg.data == [Variable("banana"), Variable("mango")] 301 | 302 | with pytest.raises(ValueError, match="already in register"): 303 | reg.add(Variable("mango")) 304 | 305 | def test_freeze(self): 306 | """Test freezing the register.""" 307 | data = [Variable("a"), Variable("b")] 308 | reg = SelfIncrementingRegister(data) 309 | reg.freeze() 310 | 311 | assert reg.frozen 312 | 313 | with pytest.raises( 314 | RegisterError, match="Register is frozen and no more data can be added." 315 | ): 316 | reg.add(Variable("abc")) 317 | 318 | def test_selfincrementing_when_frozen(self): 319 | """Test freezing the register and then attempting to access outside of scope.""" 320 | data = [Variable("a"), Variable("b")] 321 | reg = SelfIncrementingRegister(data) 322 | reg.freeze() 323 | 324 | assert reg.frozen 325 | 326 | with pytest.raises(IndexError): 327 | reg[15] 328 | -------------------------------------------------------------------------------- /tests/test_simulator/test_measurements.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import numpy as np 16 | import pytest 17 | 18 | import dwave.gate.operations as ops 19 | from dwave.gate.circuit import Circuit, CircuitError 20 | from dwave.gate.simulator.simulator import simulate 21 | 22 | 23 | class TestSimulateMeasurements: 24 | """Unit tests for simulating (mid-circuit) measurements.""" 25 | 26 | def test_measurements(self): 27 | """Test simulating a circuit with a measurement.""" 28 | circuit = Circuit(1, 1) 29 | 30 | with circuit.context as (q, c): 31 | ops.X(q[0]) 32 | m = ops.Measurement(q[0]) | c[0] 33 | 34 | simulate(circuit) 35 | 36 | assert m.bits 37 | assert m.bits[0] == c[0] 38 | assert c[0] == 1 39 | 40 | def test_measurements_little_endian(self): 41 | """Test that the correct error is raised when measuring using little-endian notation.""" 42 | circuit = Circuit(1, 1) 43 | 44 | with circuit.context as (q, c): 45 | ops.X(q[0]) 46 | ops.Measurement(q[0]) | c[0] 47 | with pytest.raises(CircuitError, match="only supports big-endian"): 48 | simulate(circuit, little_endian=True) 49 | 50 | def test_measurement_state(self): 51 | """Test accessing a state from a measurement.""" 52 | circuit = Circuit(1, 1) 53 | 54 | with circuit.context as (q, c): 55 | ops.X(q[0]) 56 | m = ops.Measurement(q[0]) | c[0] 57 | 58 | simulate(circuit) 59 | 60 | assert np.all(m.state == np.array([0, 1])) 61 | 62 | @pytest.mark.parametrize("n", [0, 1, 100]) 63 | def test_measurement_sample(self, n): 64 | """Test sampling from a measurement.""" 65 | circuit = Circuit(1, 1) 66 | 67 | with circuit.context as (q, c): 68 | ops.Hadamard(q[0]) 69 | m = ops.Measurement(q[0]) | c[0] 70 | 71 | simulate(circuit) 72 | 73 | samples = m.sample(num_samples=n) 74 | assert len(samples) == n 75 | assert all([i in ([0], [1]) for i in samples]) 76 | 77 | def test_measurement_sample_multiple_qubits(self): 78 | """Test sampling from a measurement on multiple qubits.""" 79 | circuit = Circuit(2, 2) 80 | 81 | with circuit.context as (q, c): 82 | ops.X(q[0]) 83 | m = ops.Measurement(q) | c 84 | 85 | simulate(circuit) 86 | 87 | assert m.sample() == [[1, 0]] 88 | 89 | assert m.sample([0]) == [[1]] 90 | assert m.sample([1]) == [[0]] 91 | 92 | def test_measurement_sample_bitstring(self): 93 | """Test sampling from a measurement returning bitstrings.""" 94 | circuit = Circuit(2, 2) 95 | 96 | with circuit.context as (q, c): 97 | ops.X(q[0]) 98 | m = ops.Measurement(q) | c 99 | 100 | _ = simulate(circuit) 101 | 102 | assert m.sample(num_samples=3, as_bitstring=True) == ["10", "10", "10"] 103 | 104 | def test_measurement_sample_nonexistent_qubit(self): 105 | """Test sampling from a measurement on a non-existent qubit.""" 106 | circuit = Circuit(1, 1) 107 | 108 | with circuit.context as (q, c): 109 | ops.X(q[0]) 110 | m = ops.Measurement(q) | c 111 | 112 | simulate(circuit) 113 | 114 | assert m.sample([0]) == [[1]] 115 | with pytest.raises(ValueError, match="Cannot sample qubit"): 116 | _ = m.sample([1]) 117 | 118 | def test_measurement_expval(self): 119 | """Test measuring an expectation value from a measurement.""" 120 | circuit = Circuit(1, 1) 121 | 122 | with circuit.context as (q, c): 123 | ops.Hadamard(q[0]) 124 | m = ops.Measurement(q[0]) | c[0] 125 | 126 | simulate(circuit) 127 | # expectation values are random; assert that it's between 0.4 and 0.6 128 | assert 0.5 == pytest.approx(m.expval()[0], 0.2) 129 | 130 | def test_measurement_entanglement(self): 131 | """Test measuring entangled qubits, making sure that the state 132 | collapses correctly inbetween measurments.""" 133 | circuit = Circuit(2, 2) 134 | 135 | with circuit.context as (q, c): 136 | ops.Hadamard(q[0]) 137 | ops.CNOT(q[0], q[1]) 138 | m = ops.Measurement(q) | c 139 | 140 | _ = simulate(circuit) 141 | 142 | # circuit above should only have "00" and "11" sample; 143 | # _not_ any "01" or "10" samples 144 | samples = m.sample(num_samples=10000, as_bitstring=True) 145 | assert "01" not in samples 146 | assert "10" not in samples 147 | 148 | def test_measurement_no_simulation(self): 149 | """Test a circuit with a measurement without simulating it.""" 150 | circuit = Circuit(1, 1) 151 | 152 | with circuit.context as (q, c): 153 | ops.Hadamard(q[0]) 154 | m = ops.Measurement(q[0]) 155 | 156 | with pytest.raises(CircuitError, match="Measurement has no state."): 157 | m.expval() 158 | 159 | with pytest.raises(CircuitError, match="Measurement has no state."): 160 | m.sample() 161 | 162 | assert m.state is None 163 | 164 | 165 | class TestConditionalOps: 166 | """Unit tests for running conditional operations on the simulator.""" 167 | 168 | def test_conditional_op_true(self): 169 | """Test simulating a circuit with a conditional op (true).""" 170 | circuit = Circuit(2, 1) 171 | 172 | with circuit.context as (q, c): 173 | ops.X(q[0]) 174 | ops.Measurement(q[0]) | c[0] # state is |10> 175 | ops.X(q[1]).conditional(c[0]) 176 | 177 | simulate(circuit) 178 | # should apply X on qubit 1 changing state to |11> 179 | expected = np.array([0, 0, 0, 1]) 180 | 181 | assert np.allclose(circuit.state, expected) 182 | 183 | def test_conditional_op_false(self): 184 | """Test simulating a circuit with a conditional op (false).""" 185 | circuit = Circuit(2, 1) 186 | 187 | with circuit.context as (q, c): 188 | ops.Measurement(q[0]) | c[0] # state is |00> 189 | ops.X(q[1]).conditional(c[0]) 190 | 191 | simulate(circuit) 192 | # should NOT apply X on qubit 1 leaving state in |00> 193 | expected = np.array([1, 0, 0, 0]) 194 | 195 | assert np.allclose(circuit.state, expected) 196 | 197 | def test_conditional_op_multiple_qubits_false(self): 198 | """Test simulating a circuit with a multiple conditional ops (false).""" 199 | circuit = Circuit(2, 2) 200 | 201 | with circuit.context as (q, c): 202 | ops.X(q[0]) 203 | ops.Measurement(q) | c # state is |10> 204 | x = ops.X(q[1]).conditional(c) 205 | 206 | simulate(circuit) 207 | # should NOT apply X on qubit 1 leaving state in |10> 208 | expected = np.array([0, 0, 1, 0]) 209 | 210 | assert np.allclose(circuit.state, expected) 211 | assert x.is_blocked 212 | 213 | # assert that the returned op is the circuit op 214 | assert x is circuit.circuit[-1] 215 | 216 | def test_conditional_op_multiple_qubits_true(self): 217 | """Test simulating a circuit with a multiple conditional ops (true).""" 218 | circuit = Circuit(2, 2) 219 | 220 | with circuit.context as (q, c): 221 | ops.X(q[0]) 222 | ops.X(q[1]) 223 | ops.Measurement(q) | c # state is |11> 224 | x = ops.X(q[0]).conditional(c) 225 | 226 | simulate(circuit) 227 | # should apply X on qubit 0 changing state to |01> 228 | expected = np.array([0, 1, 0, 0]) 229 | 230 | assert np.allclose(circuit.state, expected) 231 | assert not x.is_blocked 232 | 233 | # assert that the returned op is the circuit op 234 | assert x is circuit.circuit[-1] 235 | 236 | def test_conditional_op_parametric_false(self): 237 | """Test simulating a circuit with a conditional parametric op (false).""" 238 | circuit = Circuit(2, 1) 239 | 240 | with circuit.context as (q, c): 241 | ops.Measurement(q[0]) | c[0] # state is |00> 242 | rx = ops.RX(np.pi, q[1]).conditional(c[0]) 243 | 244 | simulate(circuit) 245 | # should NOT apply X on qubit 1 leaving state in |00> 246 | expected = np.array([1, 0, 0, 0]) 247 | 248 | assert np.allclose(circuit.state, expected) 249 | assert rx.is_blocked 250 | 251 | # assert that the returned op is the circuit op 252 | assert rx is circuit.circuit[-1] 253 | 254 | def test_conditional_op_parametric_true(self): 255 | """Test simulating a circuit with a conditional parametric op (true).""" 256 | circuit = Circuit(2, 1) 257 | 258 | with circuit.context as (q, c): 259 | ops.X(q[0]) 260 | ops.Measurement(q[0]) | c[0] # state is |10> 261 | rx = ops.RX(np.pi, q[1]).conditional(c[0]) 262 | 263 | simulate(circuit) 264 | # should apply RX(pi) on qubit 1 changing state to |11> (with extra global phase) 265 | expected = np.array([0, 0, 0, -1j]) 266 | 267 | assert np.allclose(circuit.state, expected) 268 | assert not rx.is_blocked 269 | 270 | # assert that the returned op is the circuit op 271 | assert rx is circuit.circuit[-1] 272 | 273 | def test_bell_state_measurement(self): 274 | """Test that measurement gives one of two correct states and sets the resulting 275 | state vector correctly for the Bell state.""" 276 | 277 | circuit = Circuit(2, 2) 278 | 279 | with circuit.context as (q, c): 280 | ops.Hadamard(q[0]) 281 | ops.CNOT(q[0], q[1]) 282 | ops.Measurement(q) | c 283 | 284 | # there is a 1 in 2^20 chance this will not test both possible outcomes 285 | for _ in range(21): 286 | for bit in circuit.bits: 287 | bit.reset() 288 | 289 | simulate(circuit) 290 | 291 | measurement = tuple(b.value for b in circuit.bits) 292 | 293 | if measurement == (0, 0): 294 | assert np.allclose(circuit.state, [1, 0, 0, 0]) 295 | elif measurement == (1, 1): 296 | assert np.allclose(circuit.state, [0, 0, 0, 1]) 297 | else: 298 | assert False 299 | 300 | def test_non_entangled_measurement(self): 301 | """Test single qubit measurement is correct on after Hadamards.""" 302 | circuit = Circuit(2, 1) 303 | 304 | with circuit.context as (q, c): 305 | ops.Hadamard(q[0]) 306 | ops.Hadamard(q[1]) 307 | ops.Measurement(q[0]) | c[0] 308 | 309 | # there is a 1 in 2^20 chance this will not test both possible outcomes 310 | for _ in range(21): 311 | for bit in circuit.bits: 312 | bit.reset() 313 | 314 | simulate(circuit) 315 | 316 | if circuit.bits[0].value == 0: 317 | assert np.allclose(circuit.state, [1 / np.sqrt(2), 1 / np.sqrt(2), 0, 0]) 318 | else: 319 | assert np.allclose(circuit.state, [0, 0, 1 / np.sqrt(2), 1 / np.sqrt(2)]) 320 | 321 | def test_measurement_rng_seed(self): 322 | """Test measurement is reproducible after setting RNG seed.""" 323 | num_qubits = 10 324 | circuit = Circuit(num_qubits, num_qubits) 325 | 326 | with circuit.context as (q, c): 327 | for i in range(num_qubits): 328 | ops.Hadamard(q[i]) 329 | ops.Measurement(q) | c 330 | 331 | simulate(circuit, rng_seed=666) 332 | expected = tuple(b.value for b in circuit.bits) 333 | 334 | for _ in range(5): 335 | for bit in circuit.bits: 336 | bit.reset() 337 | simulate(circuit, rng_seed=666) 338 | assert expected == tuple(b.value for b in circuit.bits) 339 | -------------------------------------------------------------------------------- /tests/test_simulator/test_simulator.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import numpy as np 16 | import pytest 17 | 18 | import dwave.gate.operations as ops 19 | from dwave.gate.circuit import Circuit, CircuitError 20 | from dwave.gate.operations.operations import __all__ as all_ops 21 | from dwave.gate.simulator.simulator import simulate 22 | 23 | 24 | def test_simulate_sv_empty_circuit(empty_circuit): 25 | simulate(empty_circuit) 26 | assert empty_circuit.state is None 27 | 28 | 29 | def test_simulate_sv_no_ops_one_qubit(): 30 | circuit = Circuit(1) 31 | simulate(circuit) 32 | assert np.all(circuit.state == np.array([1, 0])) 33 | 34 | 35 | def test_simulate_sv_no_ops_two_qubit(): 36 | circuit = Circuit(2) 37 | simulate(circuit) 38 | assert np.all(circuit.state == np.array([1, 0, 0, 0])) 39 | 40 | 41 | def test_simulate_dm_empty_circuit(empty_circuit): 42 | simulate(empty_circuit, mixed_state=True) 43 | assert empty_circuit.state is None 44 | 45 | 46 | def test_simulate_dm_no_ops_one_qubit(): 47 | circuit = Circuit(1) 48 | simulate(circuit, mixed_state=True) 49 | with pytest.raises(CircuitError, match="State is mixed."): 50 | circuit.state 51 | assert np.all(circuit.density_matrix == np.array([[1, 0], [0, 0]])) 52 | 53 | 54 | def test_simulate_dm_no_ops_two_qubit(): 55 | circuit = Circuit(2) 56 | simulate(circuit, mixed_state=True) 57 | pure_zero = np.zeros((4, 4)) 58 | pure_zero[0, 0] = 1 59 | with pytest.raises(CircuitError, match="State is mixed."): 60 | circuit.state 61 | assert np.all(circuit.density_matrix == pure_zero) 62 | 63 | 64 | def test_simulate_sv_not(): 65 | circuit = Circuit(1) 66 | with circuit.context as regs: 67 | ops.X(regs.q[0]) 68 | simulate(circuit) 69 | assert np.all(circuit.state == np.array([0, 1])) 70 | 71 | 72 | def test_simulate_dm_not(): 73 | circuit = Circuit(1) 74 | with circuit.context as regs: 75 | ops.X(regs.q[0]) 76 | simulate(circuit, mixed_state=True) 77 | assert np.all(circuit.density_matrix == np.array([[0, 0], [0, 1]])) 78 | 79 | 80 | def test_simulate_sv_cnot(): 81 | circuit = Circuit(2) 82 | with circuit.context as regs: 83 | ops.X(regs.q[0]) 84 | ops.CNOT(regs.q[0], regs.q[1]) 85 | simulate(circuit) 86 | # should be |11> 87 | assert np.all(circuit.state == np.array([0, 0, 0, 1])) 88 | 89 | 90 | def test_simulate_dm_cnot(): 91 | circuit = Circuit(2) 92 | with circuit.context as regs: 93 | ops.X(regs.q[0]) 94 | ops.CNOT(regs.q[0], regs.q[1]) 95 | simulate(circuit, mixed_state=True) 96 | # should be |11> 97 | pure_11 = np.zeros((4, 4)) 98 | pure_11[3, 3] = 1 99 | assert np.all(circuit.density_matrix == pure_11) 100 | 101 | 102 | def test_simulate_sv_big_endian(): 103 | circuit = Circuit(2) 104 | with circuit.context as regs: 105 | ops.X(regs.q[1]) 106 | 107 | simulate(circuit, little_endian=False) 108 | assert np.all(circuit.state == np.array([0, 1, 0, 0])) 109 | 110 | 111 | def test_simulate_sv_little_endian(): 112 | circuit = Circuit(2) 113 | with circuit.context as regs: 114 | ops.X(regs.q[1]) 115 | 116 | simulate(circuit, little_endian=True) 117 | assert np.all(circuit.state == np.array([0, 0, 1, 0])) 118 | 119 | 120 | def test_simulate_sv_swap(): 121 | circuit = Circuit(2) 122 | with circuit.context as regs: 123 | ops.X(regs.q[0]) 124 | ops.Hadamard(regs.q[0]) 125 | ops.Hadamard(regs.q[1]) 126 | ops.SWAP([regs.q[0], regs.q[1]]) 127 | 128 | simulate(circuit, little_endian=False) 129 | 130 | assert np.all(np.isclose(circuit.state, 0.5 * np.array([1, -1, 1, -1]))) 131 | 132 | 133 | def test_simulate_dm_swap(): 134 | circuit = Circuit(2) 135 | with circuit.context as regs: 136 | ops.X(regs.q[0]) 137 | ops.Hadamard(regs.q[0]) 138 | ops.Hadamard(regs.q[1]) 139 | ops.SWAP([regs.q[0], regs.q[1]]) 140 | 141 | simulate(circuit, little_endian=False) 142 | 143 | assert np.all(np.isclose(circuit.state, 0.5 * np.array([1, -1, 1, -1]))) 144 | 145 | 146 | def test_simulate_sv_ccx(): 147 | circuit = Circuit(3) 148 | with circuit.context as regs: 149 | ops.X(regs.q[0]) 150 | ops.X(regs.q[2]) 151 | ops.CCX([regs.q[0], regs.q[2], regs.q[1]]) 152 | 153 | simulate(circuit) 154 | 155 | # CCX should have taken |101> to |111> 156 | assert np.all(circuit.state == np.array([0, 0, 0, 0, 0, 0, 0, 1])) 157 | 158 | 159 | def test_simulate_dm_ccx(): 160 | circuit = Circuit(3) 161 | with circuit.context as regs: 162 | ops.X(regs.q[0]) 163 | ops.X(regs.q[2]) 164 | ops.CCX([regs.q[0], regs.q[2], regs.q[1]]) 165 | 166 | simulate(circuit, mixed_state=True) 167 | 168 | # CCX should have taken |101> to |111> 169 | pure_111 = np.zeros((8, 8)) 170 | pure_111[7, 7] = 1 171 | 172 | assert np.all(circuit.density_matrix == pure_111) 173 | 174 | 175 | def test_simulate_sv_cswap(): 176 | circuit = Circuit(3) 177 | with circuit.context as regs: 178 | ops.X(regs.q[0]) 179 | ops.X(regs.q[1]) 180 | ops.CSWAP([regs.q[1], regs.q[0], regs.q[2]]) 181 | 182 | simulate(circuit, little_endian=False) 183 | 184 | # CSWAP should have mapped |011> to |110> 185 | assert np.all(circuit.state == np.array([0, 0, 0, 1, 0, 0, 0, 0])) 186 | 187 | 188 | def test_simulate_dm_cswap(): 189 | circuit = Circuit(3) 190 | with circuit.context as regs: 191 | ops.X(regs.q[0]) 192 | ops.X(regs.q[1]) 193 | ops.CSWAP([regs.q[1], regs.q[0], regs.q[2]]) 194 | 195 | simulate(circuit, little_endian=False, mixed_state=True) 196 | 197 | # CSWAP should have mapped |011> to |110> 198 | pure_110 = np.zeros((8, 8)) 199 | pure_110[3, 3] = 1 200 | 201 | assert np.all(circuit.density_matrix == pure_110) 202 | 203 | 204 | @pytest.mark.parametrize("op", [getattr(ops, name) for name in all_ops]) 205 | @pytest.mark.parametrize("little_endian", [False, True]) 206 | @pytest.mark.parametrize("mixed_state", [False, True]) 207 | def test_simulate_all_gates(op, little_endian, mixed_state): 208 | circuit = Circuit(op.num_qubits) 209 | kwargs = {} 210 | with circuit.context as regs: 211 | if issubclass(op, ops.ParametricOperation): 212 | # TODO random parameters? 213 | kwargs["parameters"] = [0 for i in range(op.num_parameters)] 214 | kwargs["qubits"] = [regs.q[i] for i in range(op.num_qubits)] 215 | 216 | op(**kwargs) 217 | 218 | simulate(circuit, little_endian=little_endian, mixed_state=mixed_state) 219 | 220 | if mixed_state: 221 | assert 1 == pytest.approx(np.trace(circuit.density_matrix)) 222 | else: 223 | assert 1 == pytest.approx(np.sum(circuit.state * circuit.state.conj())) 224 | -------------------------------------------------------------------------------- /tests/test_tools/test_counters.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 D-Wave Systems Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import pytest 16 | 17 | from dwave.gate.tools.counters import IDCounter 18 | 19 | 20 | class TestIDCounter: 21 | """Unit tests for the ``IDCounter`` class""" 22 | 23 | def test_get_ID(self, monkeypatch): 24 | """Test retrieving a random ID number.""" 25 | monkeypatch.setattr(IDCounter, "_default_length", 4) 26 | monkeypatch.setattr(IDCounter, "_default_batch", 1000) 27 | IDCounter.reset() 28 | 29 | assert len(IDCounter.id_set) == 0 30 | 31 | id_0 = IDCounter.next() 32 | assert len(id_0) == 4 33 | assert len(IDCounter.id_set) == 1000 - 1 34 | 35 | id_1 = IDCounter.next() 36 | assert id_0 != id_1 37 | assert len(IDCounter.id_set) == 1000 - 2 38 | 39 | def test_auto_refresh_set(self, monkeypatch, mocker): 40 | """Test automatically refreshing set (without incrementing length).""" 41 | monkeypatch.setattr(IDCounter, "_default_length", 4) 42 | # set small batch size to generate new batch quickly 43 | monkeypatch.setattr(IDCounter, "_default_batch", 2) 44 | IDCounter.reset() 45 | 46 | spy = mocker.spy(IDCounter, "refresh") 47 | 48 | assert spy.call_count == 0 49 | IDCounter.next() 50 | assert spy.call_count == 1 51 | IDCounter.next() 52 | assert spy.call_count == 1 53 | IDCounter.next() 54 | assert spy.call_count == 2 55 | IDCounter.next() 56 | assert spy.call_count == 2 57 | IDCounter.next() 58 | assert spy.call_count == 3 59 | 60 | def test_auto_refresh_length(self, monkeypatch, mocker): 61 | """Test automatically refreshing set and incrementing length.""" 62 | # set small length to make sure that length is incremented 63 | monkeypatch.setattr(IDCounter, "_default_length", 1) 64 | # set small batch size to make sure that length=2 is sufficient 65 | monkeypatch.setattr(IDCounter, "_default_batch", 50) 66 | IDCounter.reset() 67 | 68 | spy = mocker.spy(IDCounter, "refresh") 69 | 70 | assert spy.call_count == 0 71 | assert IDCounter.length == 1 72 | 73 | for _ in range(len(IDCounter._alphanum)): 74 | IDCounter.next() 75 | 76 | assert spy.call_count == 1 77 | assert IDCounter.length == 2 78 | 79 | def test_manual_refresh_set(self, monkeypatch): 80 | """Test automatically refreshing set (without incrementing length).""" 81 | monkeypatch.setattr(IDCounter, "_default_length", 4) 82 | monkeypatch.setattr(IDCounter, "_default_batch", 1000) 83 | IDCounter.reset() 84 | 85 | assert len(IDCounter.id_set) == 0 86 | IDCounter.next() 87 | assert len(IDCounter.id_set) == 1000 - 1 88 | 89 | IDCounter.refresh() 90 | assert len(IDCounter.id_set) == 1000 - 1 + 1000 91 | 92 | def test_manual_refresh_length(self, monkeypatch): 93 | """Test manually refreshing set and and incrementing length.""" 94 | monkeypatch.setattr(IDCounter, "_default_length", 1) 95 | monkeypatch.setattr(IDCounter, "_default_batch", 20) 96 | IDCounter.reset() 97 | 98 | assert IDCounter.length == 1 99 | # implicit (first) refresh requesting 20 new IDs 100 | IDCounter.next() 101 | # explicit refresh requestin 20 new IDs (> `len(IDCounter._alphanums`)) 102 | # causing the length to increment to 2 103 | IDCounter.refresh() 104 | assert IDCounter.length == 2 105 | 106 | def test_reset(self, monkeypatch): 107 | """Test resetting the ID counter.""" 108 | monkeypatch.setattr(IDCounter, "_default_length", 2) 109 | monkeypatch.setattr(IDCounter, "_default_batch", 1000) 110 | IDCounter.reset() 111 | 112 | assert IDCounter.length == 2 113 | assert len(IDCounter.id_set) == 0 114 | 115 | for _ in range(60): 116 | IDCounter.next() 117 | 118 | assert IDCounter.length != 2 119 | assert len(IDCounter.id_set) != 0 120 | 121 | IDCounter.reset() 122 | 123 | assert IDCounter.length == 2 124 | assert len(IDCounter.id_set) == 0 125 | 126 | def test_insufficient_chars_warning(self, monkeypatch): 127 | """Test that a warning is raised when having an insufficient number of characters.""" 128 | monkeypatch.setattr(IDCounter, "_default_length", 4) 129 | monkeypatch.setattr(IDCounter, "_default_batch", 1000) 130 | monkeypatch.setattr(IDCounter, "_alphanum", "abcde") 131 | IDCounter.reset() 132 | 133 | with pytest.warns(match="Insufficient characters"): 134 | IDCounter.next() 135 | 136 | def test_insufficient_chars_error(self, monkeypatch): 137 | """Test that an error is raised when having less characters than requested length.""" 138 | monkeypatch.setattr(IDCounter, "_default_length", 4) 139 | monkeypatch.setattr(IDCounter, "_default_batch", 1000) 140 | # have less available chars than length 141 | monkeypatch.setattr(IDCounter, "_alphanum", "ab") 142 | IDCounter.reset() 143 | 144 | with pytest.raises( 145 | ValueError, 146 | match="ID length cannot be longer than number of unique characters available.", 147 | ): 148 | IDCounter.next() 149 | --------------------------------------------------------------------------------