├── pytest.ini ├── .github ├── CODEOWNERS ├── workflows │ ├── issue-to-project.yml │ ├── lint.yml │ ├── build-test │ └── build_and_test.yml ├── dependabot.yml └── pull_request_template.md ├── pytket └── extensions │ └── quantinuum │ ├── py.typed │ ├── backends │ ├── __init__.py │ ├── data.py │ ├── config.py │ ├── hqslib1.inc │ ├── api_wrappers.py │ └── leakage_gadget.py │ └── __init__.py ├── tests ├── test-requirements.txt ├── wasm │ ├── add1.c │ ├── add1.wasm │ ├── state.wasm │ ├── collatz.wasm │ ├── state.c │ ├── collatz.c │ ├── README.md │ ├── add1.wast │ ├── state.wast │ └── collatz.wast ├── integration │ ├── baseline │ │ └── test_view_calendar.png │ ├── local_emulator_multithreading_test.py │ ├── qir │ │ ├── qat-link_2.ll │ │ ├── qat-link.ll │ │ ├── test_pytket_qir_wasm_5-QIRProfile.ADAPTIVE.ll │ │ ├── test_pytket_qir_wasm_6-QIRProfile.ADAPTIVE.ll │ │ ├── test_pytket_qir_6.ll │ │ ├── test_pytket_qir_wasm_6-QIRProfile.PYTKET.ll │ │ └── test_pytket_qir_wasm_5-QIRProfile.PYTKET.ll │ ├── local_emulator_test.py │ └── backend_test.py └── unit │ ├── data_test.py │ ├── offline_backend_test.py │ ├── leakage_detection_test.py │ └── convert_test.py ├── MANIFEST.in ├── _metadata.py ├── .gitmodules ├── .gitignore ├── docs ├── install.sh ├── README.md ├── build-docs.sh ├── api.md ├── index.md └── changelog.md ├── mypy.ini ├── mypy-check ├── ruff.toml ├── .pylintrc ├── setup.py ├── README.md └── LICENSE /pytest.ini: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @yao-cqc 2 | -------------------------------------------------------------------------------- /pytket/extensions/quantinuum/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test-requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-timeout 3 | hypothesis 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include _metadata.py 2 | include pytket/extensions/quantinuum/py.typed 3 | -------------------------------------------------------------------------------- /_metadata.py: -------------------------------------------------------------------------------- 1 | __extension_version__ = "0.56.2" 2 | __extension_name__ = "pytket-quantinuum" 3 | -------------------------------------------------------------------------------- /tests/wasm/add1.c: -------------------------------------------------------------------------------- 1 | void init() {} 2 | 3 | int add_one(int n) { 4 | return n + 1; 5 | } 6 | -------------------------------------------------------------------------------- /tests/wasm/add1.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CQCL/pytket-quantinuum/HEAD/tests/wasm/add1.wasm -------------------------------------------------------------------------------- /tests/wasm/state.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CQCL/pytket-quantinuum/HEAD/tests/wasm/state.wasm -------------------------------------------------------------------------------- /tests/wasm/collatz.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CQCL/pytket-quantinuum/HEAD/tests/wasm/collatz.wasm -------------------------------------------------------------------------------- /tests/integration/baseline/test_view_calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CQCL/pytket-quantinuum/HEAD/tests/integration/baseline/test_view_calendar.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/pytket-docs-theming"] 2 | path = docs/pytket-docs-theming 3 | url = https://github.com/CQCL/pytket-docs-theming.git 4 | branch = main 5 | -------------------------------------------------------------------------------- /tests/wasm/state.c: -------------------------------------------------------------------------------- 1 | void init() {} 2 | 3 | static int c; 4 | 5 | void set_c(int n) { 6 | c = n; 7 | } 8 | 9 | void conditional_increment_c(int s) { 10 | if (s) c++; 11 | } 12 | 13 | int get_c() { 14 | return c; 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .eggs 2 | *.egg-info 3 | build 4 | dist 5 | *.pyc 6 | .venv 7 | .vscode 8 | .mypy_cache 9 | .hypothesis 10 | obj 11 | docs/extensions 12 | .ipynb_checkpoints 13 | pytket/extensions/quantinuum/_metadata.py 14 | .DS_Store 15 | docs/pyproject.toml 16 | docs/poetry.lock 17 | .jupyter_cache/ 18 | jupyter_execute/ 19 | .env -------------------------------------------------------------------------------- /docs/install.sh: -------------------------------------------------------------------------------- 1 | # Copy over poetry dependencies from theming repository 2 | cp pytket-docs-theming/extensions/pyproject.toml . 3 | cp pytket-docs-theming/extensions/poetry.lock . 4 | 5 | # Install the docs dependencies. Creates a .venv directory in docs 6 | poetry install 7 | 8 | # NOTE: Editable wheel should be installed separately. -------------------------------------------------------------------------------- /tests/wasm/collatz.c: -------------------------------------------------------------------------------- 1 | void init() {} 2 | 3 | unsigned collatz(unsigned n) { 4 | if (n == 0) { 5 | return 0; 6 | } 7 | unsigned m = 0; 8 | while (n != 1) { 9 | if (n & 1) { 10 | n = (3 * n + 1) / 2; 11 | } 12 | else { 13 | n /= 2; 14 | } 15 | m++; 16 | } 17 | return m; 18 | } 19 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Building the docs 2 | 3 | The docs are built by using the [pytket-docs-theming](https://github.com/CQCL/pytket-docs-theming/) repository as a git submodule. If there are issues with the docs build, feel free to open an issue or pull request there. 4 | 5 | For instructions on how to build the docs, take a look at the [extensions README](https://github.com/CQCL/pytket-docs-theming/blob/main/extensions/README.md). -------------------------------------------------------------------------------- /.github/workflows/issue-to-project.yml: -------------------------------------------------------------------------------- 1 | name: Add issues to project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | 8 | jobs: 9 | add-to-project: 10 | name: Add issue to project 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/add-to-project@v1.0.2 14 | with: 15 | project-url: https://github.com/orgs/quantinuum-dev/projects/19 16 | github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | - package-ecosystem: pip 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | groups: 14 | python-packages: 15 | patterns: 16 | - "*" 17 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please summarise the changes. 4 | 5 | # Related issues 6 | 7 | Please mention any github issues addressed by this PR. 8 | 9 | # Checklist 10 | 11 | - [ ] I have performed a self-review of my code. 12 | - [ ] I have commented hard-to-understand parts of my code. 13 | - [ ] I have made corresponding changes to the public API documentation. 14 | - [ ] I have added tests that prove my fix is effective or that my feature works. 15 | - [ ] I have updated the changelog with any user-facing changes. 16 | -------------------------------------------------------------------------------- /tests/wasm/README.md: -------------------------------------------------------------------------------- 1 | Create a C file containing 2 | 3 | ```c 4 | void init() {} 5 | ``` 6 | 7 | at the top, and any other int-to-int functions below. 8 | 9 | Run: 10 | 11 | ```shell 12 | clang --target=wasm32 -Xclang -target-abi -Xclang experimental-mv --no-standard-libraries -Wl,--export-all -Wl,--no-entry -o .wasm .c 13 | ``` 14 | 15 | to generate the wasm file. 16 | 17 | You can then run: 18 | 19 | ```shell 20 | wasm2wat .wasm -o .wast 21 | ``` 22 | 23 | to convert the WASM to human-readable text format. 24 | -------------------------------------------------------------------------------- /docs/build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -rf build/ 3 | 4 | # Move theming elements into the docs folder 5 | cp -R pytket-docs-theming/_static . 6 | cp -R pytket-docs-theming/quantinuum-sphinx . 7 | cp pytket-docs-theming/conf.py . 8 | 9 | # Get the name of the project 10 | EXTENSION_NAME="$(basename "$(dirname `pwd`)")" 11 | 12 | # Build the docs. Ensure we have the correct project title. 13 | sphinx-build -W -b html -D html_title="$EXTENSION_NAME" . build || exit 1 14 | 15 | # TODO Add `-W` flag to the command below. 16 | sphinx-build -v -b coverage . build/coverage || exit 1 17 | 18 | # Remove copied files. This ensures reusability. 19 | rm -r _static 20 | rm -r quantinuum-sphinx 21 | rm conf.py 22 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | python_version = 3.10 3 | warn_unused_configs = True 4 | 5 | disallow_untyped_decorators = False 6 | disallow_untyped_calls = True 7 | disallow_untyped_defs = True 8 | disallow_incomplete_defs = True 9 | 10 | no_implicit_optional = True 11 | strict_optional = True 12 | namespace_packages = True 13 | 14 | check_untyped_defs = True 15 | 16 | warn_redundant_casts = True 17 | warn_unused_ignores = True 18 | warn_no_return = False 19 | warn_return_any = True 20 | warn_unreachable = True 21 | 22 | [mypy-pytest.*] 23 | ignore_missing_imports = True 24 | ignore_errors = True 25 | 26 | [mypy-lark.*] 27 | ignore_missing_imports = True 28 | ignore_errors = True 29 | 30 | [mypy-sympy.*] 31 | ignore_missing_imports = True 32 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint python projects 2 | 3 | on: 4 | pull_request: {} 5 | workflow_dispatch: {} 6 | 7 | push: 8 | branches: 9 | - 'wheel/**' 10 | - 'runci/**' 11 | 12 | jobs: 13 | lint: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v5 19 | - name: Set up Python 3.x 20 | uses: actions/setup-python@v6 21 | with: 22 | python-version: '3.x' 23 | - name: Update pip 24 | run: pip install --upgrade pip 25 | - name: Install formatters and linters 26 | run: pip install ruff 27 | - name: Run ruff lint 28 | run: | 29 | ruff check . 30 | - name: Check formatting 31 | run: | 32 | ruff format --check . 33 | -------------------------------------------------------------------------------- /mypy-check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -evu 3 | 4 | # single argument = root directory of module to test 5 | # Requires mypy >= 0.800 6 | 7 | MODULEDIR=$1 8 | 9 | # set MYPYPATH 10 | MYPYPATH="." 11 | for MOD in $(ls -d "$MODULEDIR"*); do 12 | MOD_PATH="$(cd "$MOD" && pwd)" 13 | MYPYPATH="$MYPYPATH:$MOD_PATH" 14 | done 15 | export MYPYPATH="$MYPYPATH" 16 | 17 | ROOT_INIT_FILE=$(python -c "from importlib.util import find_spec; print(find_spec('pytket').origin)") 18 | 19 | # remove pytket root init file 20 | mv "$ROOT_INIT_FILE" "$ROOT_INIT_FILE.ignore" 21 | 22 | set +e 23 | mypy --config-file=mypy.ini --no-incremental -p pytket -p tests 24 | STATUS=$? 25 | set -e 26 | 27 | # reset init file 28 | mv "$ROOT_INIT_FILE.ignore" "$ROOT_INIT_FILE" 29 | 30 | exit $STATUS 31 | -------------------------------------------------------------------------------- /pytket/extensions/quantinuum/backends/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 .api_wrappers import QuantinuumAPI, QuantinuumAPIOffline 16 | from .data import H2 17 | from .leakage_gadget import prune_shots_detected_as_leaky 18 | from .quantinuum import ( 19 | Language, 20 | QuantinuumBackend, 21 | QuantinuumBackendCompilationConfig, 22 | QuantinuumBackendData, 23 | have_pecos, 24 | ) 25 | -------------------------------------------------------------------------------- /pytket/extensions/quantinuum/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 | # _metadata.py is copied to the folder after installation. 16 | from ._metadata import __extension_name__, __extension_version__ 17 | from .backends import ( 18 | H2, 19 | Language, 20 | QuantinuumAPI, 21 | QuantinuumAPIOffline, 22 | QuantinuumBackend, 23 | QuantinuumBackendCompilationConfig, 24 | QuantinuumBackendData, 25 | have_pecos, 26 | prune_shots_detected_as_leaky, 27 | ) 28 | -------------------------------------------------------------------------------- /tests/integration/local_emulator_multithreading_test.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 | 16 | import pytest 17 | from pytket.circuit import Circuit 18 | 19 | from pytket.extensions.quantinuum import QuantinuumBackend, have_pecos 20 | 21 | 22 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 23 | def test_multithreading() -> None: 24 | b = QuantinuumBackend("H2-1LE") 25 | c0 = Circuit(2).H(0).CX(0, 1).measure_all() 26 | c = b.get_compiled_circuit(c0) 27 | h = b.process_circuit(c, n_shots=10, multithreading=True) 28 | r = b.get_result(h) 29 | counts = r.get_counts() 30 | assert all(x0 == x1 for x0, x1 in counts) 31 | -------------------------------------------------------------------------------- /tests/wasm/add1.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (type (;1;) (func (param i32) (result i32))) 4 | (func $__wasm_call_ctors (type 0)) 5 | (func $init (type 0) 6 | return) 7 | (func $add_one (type 1) (param i32) (result i32) 8 | (local i32 i32 i32 i32 i32 i32) 9 | global.get $__stack_pointer 10 | local.set 1 11 | i32.const 16 12 | local.set 2 13 | local.get 1 14 | local.get 2 15 | i32.sub 16 | local.set 3 17 | local.get 3 18 | local.get 0 19 | i32.store offset=12 20 | local.get 3 21 | i32.load offset=12 22 | local.set 4 23 | i32.const 1 24 | local.set 5 25 | local.get 4 26 | local.get 5 27 | i32.add 28 | local.set 6 29 | local.get 6 30 | return) 31 | (memory (;0;) 2) 32 | (global $__stack_pointer (mut i32) (i32.const 66560)) 33 | (global (;1;) i32 (i32.const 1024)) 34 | (global (;2;) i32 (i32.const 1024)) 35 | (global (;3;) i32 (i32.const 1024)) 36 | (global (;4;) i32 (i32.const 66560)) 37 | (global (;5;) i32 (i32.const 0)) 38 | (global (;6;) i32 (i32.const 1)) 39 | (export "memory" (memory 0)) 40 | (export "__wasm_call_ctors" (func $__wasm_call_ctors)) 41 | (export "init" (func $init)) 42 | (export "add_one" (func $add_one)) 43 | (export "__dso_handle" (global 1)) 44 | (export "__data_end" (global 2)) 45 | (export "__global_base" (global 3)) 46 | (export "__heap_base" (global 4)) 47 | (export "__memory_base" (global 5)) 48 | (export "__table_base" (global 6))) 49 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | exclude = [ 2 | "docs/pytket-docs-theming", 3 | "docs/examples/*" 4 | ] 5 | 6 | 7 | target-version = "py310" 8 | 9 | lint.select = [ 10 | "A", 11 | "AIR", 12 | # "ANN", 13 | # "ARG", # TODO 14 | "ASYNC", 15 | "B", 16 | "BLE", 17 | # "C", 18 | "C4", 19 | # "C90", 20 | "COM", 21 | # "CPY", 22 | # "D", 23 | "DJ", 24 | # "DOC", 25 | "DTZ", 26 | "E", 27 | # "EM", 28 | # "ERA", # TODO 29 | "EXE", 30 | "F", 31 | "FA", 32 | # "FAST", 33 | # "FBT", 34 | # "FIX", 35 | "FLY", 36 | "FURB", 37 | "G", 38 | "I", 39 | # "ICN", 40 | # "INP", 41 | "INT", 42 | "ISC", 43 | "LOG", 44 | # "N", 45 | "NPY", 46 | # "PD", 47 | "PERF", 48 | # "PGH", 49 | "PIE", 50 | "PL", 51 | # "PT", 52 | # "PTH", # TODO 53 | # "PYI", # TODO 54 | "Q", 55 | "R", 56 | "RET", 57 | "RSE", 58 | "RUF", 59 | # "S", 60 | "SIM", 61 | "SLF", 62 | "SLOT", 63 | "T10", 64 | "T20", 65 | "TCH", 66 | # "TD", 67 | # "TID", 68 | # "TRY", 69 | "UP", 70 | "W", 71 | "YTT", 72 | ] 73 | 74 | lint.ignore = [ 75 | "E501", # Allow long lines in strings 76 | "E731", # OK to assign to lambdas 77 | "E741", # Allow variable names like "l" 78 | "F403", # Allow wildcard imports in init files 79 | "COM812", # flake8-commas "Trailing comma missing" 80 | ] 81 | 82 | [lint.per-file-ignores] 83 | "__init__.py" = ["F401"] 84 | "**/{tests}/*" = ["PLR2004"] 85 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | max-line-length=88 3 | output-format=colorized 4 | score=no 5 | reports=no 6 | disable=all 7 | enable= 8 | anomalous-backslash-in-string, 9 | assert-on-tuple, 10 | bad-indentation, 11 | bad-option-value, 12 | bad-reversed-sequence, 13 | bad-super-call, 14 | consider-merging-isinstance, 15 | continue-in-finally, 16 | dangerous-default-value, 17 | duplicate-argument-name, 18 | expression-not-assigned, 19 | function-redefined, 20 | inconsistent-mro, 21 | init-is-generator, 22 | line-too-long, 23 | lost-exception, 24 | missing-kwoa, 25 | mixed-line-endings, 26 | not-callable, 27 | no-value-for-parameter, 28 | nonexistent-operator, 29 | not-in-loop, 30 | pointless-statement, 31 | redefined-builtin, 32 | return-arg-in-generator, 33 | return-in-init, 34 | return-outside-function, 35 | simplifiable-if-statement, 36 | syntax-error, 37 | too-many-function-args, 38 | trailing-whitespace, 39 | undefined-variable, 40 | unexpected-keyword-arg, 41 | unhashable-dict-key, 42 | unnecessary-pass, 43 | unreachable, 44 | unrecognized-inline-option, 45 | unused-import, 46 | unnecessary-semicolon, 47 | unused-variable, 48 | unused-wildcard-import, 49 | wildcard-import, 50 | wrong-import-order, 51 | wrong-import-position, 52 | yield-outside-function 53 | 54 | # Ignore long lines containing URLs or pylint or mypy directives. 55 | ignore-long-lines=^(.*#\w*pylint: disable.*|.*# type: ignore.*|\s*(# )??)$ 56 | -------------------------------------------------------------------------------- /.github/workflows/build-test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -evu 3 | 4 | # Usage: 5 | # 6 | # build-test [mypy|nomypy] 7 | # 8 | # Arguments: 9 | # - mypy: include mypy check ("mypy" or "nomypy") 10 | # 11 | # Environment variables used: 12 | # - GITHUB_WORKSPACE: workspace directory 13 | # 14 | # WARNING: running this locally will delete any local files that 15 | # aren't strictly part of the git tree, including gitignored files! 16 | 17 | MODULE=pytket-quantinuum 18 | 19 | MYPY=$1 20 | 21 | PLAT=`python -c 'import platform; print(platform.system())'` 22 | 23 | PYVER=`python -c 'import sys; print(".".join(map(str, sys.version_info[:2])))'` 24 | 25 | git clean -dfx 26 | 27 | echo "Module to test: ${MODULE}" 28 | 29 | MODULEDIR="${GITHUB_WORKSPACE}" 30 | 31 | ARTIFACTSDIR=${GITHUB_WORKSPACE}/wheelhouse 32 | 33 | rm -rf ${ARTIFACTSDIR} && mkdir ${ARTIFACTSDIR} 34 | 35 | python -m pip install --upgrade pip wheel build 36 | 37 | # Generate and install the package 38 | python -m build 39 | for w in dist/*.whl ; do 40 | python -m pip install $w[pecos] 41 | cp $w ${ARTIFACTSDIR} 42 | done 43 | 44 | # Test and mypy: 45 | if [[ "${MYPY}" = "mypy" ]] 46 | then 47 | python -m pip install --upgrade mypy 48 | fi 49 | 50 | cd ${GITHUB_WORKSPACE}/tests 51 | 52 | python -m pip install --pre -r test-requirements.txt 53 | 54 | # update the pytket version to the lastest (pre) release 55 | python -m pip install --upgrade --pre pytket~=2.0 56 | 57 | # install the latest compatible pytket-qir version 58 | 59 | pytest --doctest-modules 60 | 61 | cd .. 62 | 63 | if [[ "${MYPY}" = "mypy" ]] 64 | then 65 | ${GITHUB_WORKSPACE}/mypy-check ${GITHUB_WORKSPACE}} 66 | fi 67 | -------------------------------------------------------------------------------- /pytket/extensions/quantinuum/backends/data.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 dataclasses import dataclass 16 | 17 | from pytket.circuit import OpType 18 | 19 | 20 | @dataclass 21 | class QuantinuumBackendData: 22 | """Data characterizing a QuantinuumBackend. 23 | 24 | * `n_qubits`: maximum number of qubits available 25 | * `n_cl_reg`: maximum number of classical registers available 26 | * `gate_set`: set of available native gates 27 | * `local_emulator`: whether the backend is a local emulator 28 | """ 29 | 30 | n_qubits: int 31 | n_cl_reg: int 32 | gate_set: frozenset[OpType] = frozenset( 33 | { 34 | OpType.Rz, 35 | OpType.PhasedX, 36 | OpType.ZZPhase, 37 | OpType.Measure, 38 | OpType.Reset, 39 | } 40 | ) 41 | local_emulator: bool = False 42 | 43 | 44 | """Data characterizing H2 devices and emulators""" 45 | H2 = QuantinuumBackendData( 46 | n_qubits=56, 47 | n_cl_reg=4000, 48 | gate_set=frozenset( 49 | {OpType.PhasedX, OpType.Rz, OpType.TK2, OpType.ZZMax, OpType.ZZPhase} 50 | ), 51 | ) 52 | -------------------------------------------------------------------------------- /pytket/extensions/quantinuum/backends/config.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 dataclasses import dataclass 16 | from typing import Any, ClassVar 17 | 18 | from pytket.config import PytketExtConfig 19 | 20 | 21 | @dataclass 22 | class QuantinuumConfig(PytketExtConfig): 23 | """Holds config parameters for pytket-quantinuum.""" 24 | 25 | ext_dict_key: ClassVar[str] = "quantinuum" 26 | 27 | username: str | None 28 | 29 | refresh_token: str | None 30 | 31 | id_token: str | None 32 | 33 | refresh_token_timeout: str | None 34 | 35 | id_token_timeout: str | None 36 | 37 | @classmethod 38 | def from_extension_dict( 39 | cls: type["QuantinuumConfig"], ext_dict: dict[str, Any] 40 | ) -> "QuantinuumConfig": 41 | return cls( 42 | ext_dict.get("username"), 43 | ext_dict.get("refresh_token"), 44 | ext_dict.get("id_token"), 45 | ext_dict.get("refresh_token_timeout"), 46 | ext_dict.get("id_token_timeout"), 47 | ) 48 | 49 | 50 | def set_quantinuum_config(username: str | None) -> None: 51 | """Set default value for Quantinuum username. 52 | Can be overriden in backend construction.""" 53 | hconfig = QuantinuumConfig.from_default_config_file() 54 | hconfig.username = username 55 | hconfig.update_default_config_file() 56 | -------------------------------------------------------------------------------- /tests/unit/data_test.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 | from pytket.circuit import Circuit 17 | 18 | from pytket.extensions.quantinuum import ( 19 | H2, 20 | QuantinuumBackend, 21 | QuantinuumBackendData, 22 | have_pecos, 23 | ) 24 | 25 | 26 | def test_backend_data() -> None: 27 | for data in [H2]: 28 | b = QuantinuumBackend("test", data=data) 29 | c = Circuit(2).H(0).CX(0, 1).measure_all() 30 | p = b.default_compilation_pass(optimisation_level=2) 31 | p.apply(c) 32 | assert ( 33 | c 34 | == Circuit(2) 35 | .PhasedX(3.5, 0.5, 0) 36 | .PhasedX(2.5, 0.5, 1) 37 | .ZZPhase(0.5, 0, 1) 38 | .PhasedX(0.5, 0.0, 1) 39 | .add_phase(1.75) 40 | .measure_all() 41 | ) 42 | 43 | 44 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 45 | def test_backend_data_local_emulator() -> None: 46 | data = QuantinuumBackendData(n_qubits=20, n_cl_reg=10, local_emulator=True) 47 | b = QuantinuumBackend("test", data=data) 48 | c = Circuit(2).H(0).CX(0, 1).measure_all() 49 | c1 = b.get_compiled_circuit(c) 50 | h = b.process_circuit(c1, n_shots=10) 51 | r = b.get_result(h) 52 | counts = r.get_counts() 53 | assert all(x0 == x1 for x0, x1 in counts) 54 | assert counts.total() == 10 55 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # API documentation 2 | 3 | The pytket-quantinuum extension allows submission of pytket circuits to Quantinuum systems (and emulators) via the {py:class}`~.QuantinuumBackend`. 4 | 5 | See the [pytket-quantinuum section](https://docs.quantinuum.com/systems/trainings/getting_started/pytket_quantinuum/pytket_quantinuum.html) of the documentation website for some example usage. 6 | 7 | ```{eval-rst} 8 | .. automodule:: pytket.extensions.quantinuum 9 | .. automodule:: pytket.extensions.quantinuum._metadata 10 | .. automodule:: pytket.extensions.quantinuum.backends 11 | .. automodule:: pytket.extensions.quantinuum.backends.quantinuum 12 | 13 | .. autoenum:: Language 14 | :members: 15 | 16 | .. autoclass:: QuantinuumBackend 17 | :show-inheritance: 18 | :special-members: __init__ 19 | :members: 20 | 21 | .. automethod:: pass_from_info 22 | 23 | 24 | .. autoclass:: QuantinuumBackendCompilationConfig 25 | :members: 26 | 27 | .. autoexception:: BackendOfflineError 28 | .. autoexception:: BatchingUnsupported 29 | .. autoexception:: DeviceNotAvailable 30 | .. autoexception:: GetResultFailed 31 | .. autoexception:: LanguageUnsupported 32 | .. autoexception:: MaxShotsExceeded 33 | .. autoexception:: NoSyntaxChecker 34 | 35 | .. automodule:: pytket.extensions.quantinuum.backends.data 36 | 37 | .. autoclass:: QuantinuumBackendData 38 | :members: 39 | .. autodata:: H2 40 | 41 | .. automodule:: pytket.extensions.quantinuum.backends.api_wrappers 42 | 43 | .. autoclass:: QuantinuumAPI 44 | 45 | .. automethod:: get_machine_list 46 | 47 | .. autoclass:: QuantinuumAPIOffline 48 | :members: 49 | 50 | .. automodule:: pytket.extensions.quantinuum.backends.config 51 | :members: 52 | 53 | .. autoexception:: pytket.extensions.quantinuum.backends.quantinuum.WasmUnsupported 54 | 55 | .. autoexception:: pytket.extensions.quantinuum.backends.api_wrappers.QuantinuumAPIError 56 | ``` 57 | -------------------------------------------------------------------------------- /tests/integration/qir/qat-link_2.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'result_tag.bc' 2 | source_filename = "qat-link" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | @0 = internal constant [5 x i8] c"0_t0\00" 8 | @1 = internal constant [5 x i8] c"0_t1\00" 9 | 10 | define void @Quantinuum__EntangledState() #0 { 11 | entry: 12 | call void @__quantum__qis__h__body(%Qubit* null) 13 | call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* nonnull inttoptr (i64 1 to %Qubit*)) 14 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 15 | call void @__quantum__qis__reset__body(%Qubit* null) 16 | call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 1 to %Qubit*), %Result* nonnull inttoptr (i64 1 to %Result*)) 17 | call void @__quantum__qis__reset__body(%Qubit* nonnull inttoptr (i64 1 to %Qubit*)) 18 | call void @__quantum__rt__tuple_start_record_output() 19 | call void @__quantum__rt__result_record_output(%Result* null, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @0, i32 0, i32 0)) 20 | call void @__quantum__rt__result_record_output(%Result* nonnull inttoptr (i64 1 to %Result*), i8* getelementptr inbounds ([5 x i8], [5 x i8]* @1, i32 0, i32 0)) 21 | call void @__quantum__rt__tuple_end_record_output() 22 | ret void 23 | } 24 | 25 | declare %Qubit* @__quantum__rt__qubit_allocate() 26 | 27 | declare void @__quantum__qis__h__body(%Qubit*) 28 | 29 | declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*) 30 | 31 | declare %Result* @__quantum__qis__m__body(%Qubit*) 32 | 33 | declare void @__quantum__qis__reset__body(%Qubit*) 34 | 35 | declare void @__quantum__rt__qubit_release(%Qubit*) 36 | 37 | declare void @__quantum__rt__tuple_start_record_output() 38 | 39 | declare void @__quantum__rt__result_record_output(%Result*, i8*) 40 | 41 | declare void @__quantum__rt__tuple_end_record_output() 42 | 43 | declare void @__quantum__qis__mz__body(%Qubit*, %Result*) 44 | 45 | attributes #0 = { "EntryPoint" "maxQubitIndex"="1" "maxResultIndex"="1" "requiredQubits"="2" "requiredResults"="2" } -------------------------------------------------------------------------------- /tests/integration/qir/qat-link.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'result_tag.bc' 2 | source_filename = "qat-link" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | @0 = internal constant [5 x i8] c"0_t0\00" 8 | @1 = internal constant [5 x i8] c"0_t1\00" 9 | 10 | define void @Quantinuum__EntangledState() #0 { 11 | entry: 12 | call void @__quantum__qis__h__body(%Qubit* null) 13 | call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* nonnull inttoptr (i64 1 to %Qubit*)) 14 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 15 | call void @__quantum__qis__reset__body(%Qubit* null) 16 | call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 1 to %Qubit*), %Result* nonnull inttoptr (i64 1 to %Result*)) 17 | call void @__quantum__qis__reset__body(%Qubit* nonnull inttoptr (i64 1 to %Qubit*)) 18 | call void @__quantum__rt__tuple_start_record_output() 19 | call void @__quantum__rt__result_record_output(%Result* null, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @0, i32 0, i32 0)) 20 | call void @__quantum__rt__result_record_output(%Result* nonnull inttoptr (i64 1 to %Result*), i8* getelementptr inbounds ([5 x i8], [5 x i8]* @1, i32 0, i32 0)) 21 | call void @__quantum__rt__tuple_end_record_output() 22 | ret void 23 | } 24 | 25 | declare %Qubit* @__quantum__rt__qubit_allocate() 26 | 27 | declare void @__quantum__qis__h__body(%Qubit*) 28 | 29 | declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*) 30 | 31 | declare %Result* @__quantum__qis__m__body(%Qubit*) 32 | 33 | declare void @__quantum__qis__reset__body(%Qubit*) 34 | 35 | declare void @__quantum__rt__qubit_release(%Qubit*) 36 | 37 | declare void @__quantum__rt__tuple_start_record_output() 38 | 39 | declare void @__quantum__rt__result_record_output(%Result*, i8*) 40 | 41 | declare void @__quantum__rt__tuple_end_record_output() 42 | 43 | declare void @__quantum__qis__mz__body(%Qubit*, %Result*) 44 | 45 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="3" "required_num_results"="3" } -------------------------------------------------------------------------------- /tests/wasm/state.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (type (;1;) (func (param i32))) 4 | (type (;2;) (func (result i32))) 5 | (func $__wasm_call_ctors (type 0)) 6 | (func $init (type 0) 7 | return) 8 | (func $set_c (type 1) (param i32) 9 | (local i32 i32 i32 i32 i32) 10 | global.get $__stack_pointer 11 | local.set 1 12 | i32.const 16 13 | local.set 2 14 | local.get 1 15 | local.get 2 16 | i32.sub 17 | local.set 3 18 | local.get 3 19 | local.get 0 20 | i32.store offset=12 21 | local.get 3 22 | i32.load offset=12 23 | local.set 4 24 | i32.const 0 25 | local.set 5 26 | local.get 5 27 | local.get 4 28 | i32.store offset=1024 29 | return) 30 | (func $conditional_increment_c (type 1) (param i32) 31 | (local i32 i32 i32 i32 i32 i32 i32 i32 i32) 32 | global.get $__stack_pointer 33 | local.set 1 34 | i32.const 16 35 | local.set 2 36 | local.get 1 37 | local.get 2 38 | i32.sub 39 | local.set 3 40 | local.get 3 41 | local.get 0 42 | i32.store offset=12 43 | local.get 3 44 | i32.load offset=12 45 | local.set 4 46 | block ;; label = @1 47 | local.get 4 48 | i32.eqz 49 | br_if 0 (;@1;) 50 | i32.const 0 51 | local.set 5 52 | local.get 5 53 | i32.load offset=1024 54 | local.set 6 55 | i32.const 1 56 | local.set 7 57 | local.get 6 58 | local.get 7 59 | i32.add 60 | local.set 8 61 | i32.const 0 62 | local.set 9 63 | local.get 9 64 | local.get 8 65 | i32.store offset=1024 66 | end 67 | return) 68 | (func $get_c (type 2) (result i32) 69 | (local i32 i32) 70 | i32.const 0 71 | local.set 0 72 | local.get 0 73 | i32.load offset=1024 74 | local.set 1 75 | local.get 1 76 | return) 77 | (memory (;0;) 2) 78 | (global $__stack_pointer (mut i32) (i32.const 66576)) 79 | (global (;1;) i32 (i32.const 1024)) 80 | (global (;2;) i32 (i32.const 1028)) 81 | (global (;3;) i32 (i32.const 1024)) 82 | (global (;4;) i32 (i32.const 66576)) 83 | (global (;5;) i32 (i32.const 0)) 84 | (global (;6;) i32 (i32.const 1)) 85 | (export "memory" (memory 0)) 86 | (export "__wasm_call_ctors" (func $__wasm_call_ctors)) 87 | (export "init" (func $init)) 88 | (export "set_c" (func $set_c)) 89 | (export "conditional_increment_c" (func $conditional_increment_c)) 90 | (export "get_c" (func $get_c)) 91 | (export "__dso_handle" (global 1)) 92 | (export "__data_end" (global 2)) 93 | (export "__global_base" (global 3)) 94 | (export "__heap_base" (global 4)) 95 | (export "__memory_base" (global 5)) 96 | (export "__table_base" (global 6))) 97 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 | import shutil 17 | from pathlib import Path 18 | 19 | from setuptools import find_namespace_packages, setup # type: ignore 20 | 21 | metadata: dict = {} 22 | with open("_metadata.py") as fp: 23 | exec(fp.read(), metadata) 24 | shutil.copy( 25 | "_metadata.py", 26 | os.path.join("pytket", "extensions", "quantinuum", "_metadata.py"), 27 | ) 28 | 29 | 30 | setup( 31 | name="pytket-quantinuum", 32 | version=metadata["__extension_version__"], 33 | author="TKET development team", 34 | author_email="tket-support@quantinuum.com", 35 | python_requires=">=3.10", 36 | project_urls={ 37 | "Documentation": "https://docs.quantinuum.com/tket/extensions/pytket-quantinuum/index.html", 38 | "Source": "https://github.com/CQCL/pytket-quantinuum", 39 | "Tracker": "https://github.com/CQCL/pytket-quantinuum/issues", 40 | }, 41 | description="Extension for pytket, providing access to Quantinuum backends", 42 | long_description=(Path(__file__).parent / "README.md").read_text(), 43 | long_description_content_type="text/markdown", 44 | license="Apache 2", 45 | packages=find_namespace_packages(include=["pytket.*"]), 46 | include_package_data=True, 47 | install_requires=[ 48 | "pytket >= 2.9.3", 49 | "pytket-qir >= 0.25.0", 50 | "numpy >= 1.26.4", 51 | ], 52 | extras_require={ 53 | "pecos": ["pytket-pecos ~= 0.2.2", "quantum-pecos == 0.7.0.dev4"], 54 | }, 55 | classifiers=[ 56 | "Environment :: Console", 57 | "Programming Language :: Python :: 3.10", 58 | "Programming Language :: Python :: 3.11", 59 | "Programming Language :: Python :: 3.12", 60 | "License :: OSI Approved :: Apache Software License", 61 | "Operating System :: MacOS :: MacOS X", 62 | "Operating System :: POSIX :: Linux", 63 | "Operating System :: Microsoft :: Windows", 64 | "Intended Audience :: Developers", 65 | "Intended Audience :: Science/Research", 66 | "Topic :: Scientific/Engineering", 67 | ], 68 | zip_safe=False, 69 | ) 70 | -------------------------------------------------------------------------------- /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | push: 8 | branches: 9 | - main 10 | - 'wheel/**' 11 | - 'runci/**' 12 | release: 13 | types: 14 | - created 15 | - edited 16 | schedule: 17 | # 04:00 every Tuesday morning 18 | - cron: '0 4 * * 2' 19 | workflow_dispatch: 20 | 21 | jobs: 22 | quantinuum-checks: 23 | name: Quantinuum - Build and test module 24 | strategy: 25 | matrix: 26 | os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] 27 | pyver: ['3.10', '3.12'] 28 | runs-on: ${{ matrix.os }} 29 | steps: 30 | - uses: actions/checkout@v5 31 | with: 32 | fetch-depth: '0' 33 | submodules: recursive 34 | - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/* 35 | - name: Set up Python 36 | uses: actions/setup-python@v6 37 | with: 38 | python-version: ${{ matrix.pyver }} 39 | - name: Build and test 40 | shell: bash 41 | run: | 42 | ./.github/workflows/build-test mypy 43 | - uses: actions/upload-artifact@v4 44 | if: (github.event_name == 'release' || contains(github.ref, 'refs/heads/wheel')) && (matrix.pyver == '3.10' ) 45 | with: 46 | name: artefact-${{ matrix.os }} 47 | path: wheelhouse/ 48 | - name: Install poetry 49 | run: pip install poetry 50 | - name: Install docs dependencies 51 | if: (matrix.os == 'ubuntu-latest') && (github.event_name == 'pull_request' || github.event_name == 'schedule' ) 52 | run: | 53 | cd docs && bash ./install.sh 54 | poetry run pip install '../.[pecos]' 55 | - name: Build docs 56 | if: (matrix.os == 'ubuntu-latest') && (matrix.pyver == '3.12') && (github.event_name == 'pull_request' || github.event_name == 'schedule' ) 57 | timeout-minutes: 20 58 | run: | 59 | cd docs && poetry run bash ./build-docs.sh 60 | 61 | publish_to_pypi: 62 | name: Publish to pypi 63 | if: github.event_name == 'release' 64 | needs: quantinuum-checks 65 | runs-on: ubuntu-latest 66 | steps: 67 | - name: Download all wheels 68 | # downloading all three files into the wheelhouse 69 | # all files are identical, so there will only be one file 70 | uses: actions/download-artifact@v5 71 | with: 72 | path: wheelhouse 73 | pattern: artefact-* 74 | merge-multiple: true 75 | - name: Put them all in the dist folder 76 | run: | 77 | mkdir dist 78 | for w in `find wheelhouse/ -type f -name "*.whl"` ; do cp $w dist/ ; done 79 | - name: Publish wheels 80 | uses: pypa/gh-action-pypi-publish@release/v1 81 | with: 82 | user: __token__ 83 | password: ${{ secrets.PYPI_PYTKET_QUANTINUUM_API_TOKEN }} 84 | verbose: true 85 | -------------------------------------------------------------------------------- /tests/integration/qir/test_pytket_qir_wasm_5-QIRProfile.ADAPTIVE.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_pytket_qir_wasm_5-QIRProfile.ADAPTIVE' 2 | source_filename = "test_pytket_qir_wasm_5-QIRProfile.ADAPTIVE" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | @0 = internal constant [2 x i8] c"c\00" 8 | @1 = internal constant [3 x i8] c"c0\00" 9 | @2 = internal constant [3 x i8] c"c1\00" 10 | 11 | define void @main() #0 { 12 | entry: 13 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 14 | %0 = call i1 @__quantum__qis__read_result__body(%Result* null) 15 | %1 = zext i1 %0 to i64 16 | %2 = mul i64 %1, 1 17 | %3 = or i64 %2, 0 18 | %4 = sub i64 1, %1 19 | %5 = mul i64 %4, 1 20 | %6 = xor i64 9223372036854775807, %5 21 | %7 = and i64 %6, %3 22 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 23 | %8 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) 24 | %9 = zext i1 %8 to i64 25 | %10 = mul i64 %9, 2 26 | %11 = or i64 %10, %7 27 | %12 = sub i64 1, %9 28 | %13 = mul i64 %12, 2 29 | %14 = xor i64 9223372036854775807, %13 30 | %15 = and i64 %14, %11 31 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) 32 | %16 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 2 to %Result*)) 33 | %17 = zext i1 %16 to i64 34 | %18 = mul i64 %17, 4 35 | %19 = or i64 %18, %15 36 | %20 = sub i64 1, %17 37 | %21 = mul i64 %20, 4 38 | %22 = xor i64 9223372036854775807, %21 39 | %23 = and i64 %22, %19 40 | %24 = call i64 @add_one(i64 1) 41 | call void @__quantum__rt__int_record_output(i64 %23, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @0, i32 0, i32 0)) 42 | call void @__quantum__rt__int_record_output(i64 1, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @1, i32 0, i32 0)) 43 | call void @__quantum__rt__int_record_output(i64 %24, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @2, i32 0, i32 0)) 44 | ret void 45 | } 46 | 47 | declare i1 @__quantum__qis__read_result__body(%Result*) 48 | 49 | declare void @__quantum__rt__int_record_output(i64, i8*) 50 | 51 | declare void @init() #1 52 | 53 | declare i64 @add_one(i64) #1 54 | 55 | declare i64 @multi(i64, i64) #1 56 | 57 | declare i64 @add_two(i64) #1 58 | 59 | declare i64 @add_eleven(i64) #1 60 | 61 | declare void @no_return(i64) #1 62 | 63 | declare i64 @no_parameters() #1 64 | 65 | declare i64 @new_function() #1 66 | 67 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #2 68 | 69 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="6" "required_num_results"="6" } 70 | attributes #1 = { "wasm" } 71 | attributes #2 = { "irreversible" } 72 | 73 | !llvm.module.flags = !{!0, !1, !2, !3} 74 | 75 | !0 = !{i32 1, !"qir_major_version", i32 1} 76 | !1 = !{i32 7, !"qir_minor_version", i32 0} 77 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 78 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 79 | -------------------------------------------------------------------------------- /tests/integration/qir/test_pytket_qir_wasm_6-QIRProfile.ADAPTIVE.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_pytket_qir_wasm_6-QIRProfile.ADAPTIVE' 2 | source_filename = "test_pytket_qir_wasm_6-QIRProfile.ADAPTIVE" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | @0 = internal constant [2 x i8] c"c\00" 8 | @1 = internal constant [3 x i8] c"c0\00" 9 | @2 = internal constant [3 x i8] c"c1\00" 10 | 11 | define void @main() #0 { 12 | entry: 13 | call void @__quantum__qis__mz__body(%Qubit* null, %Result* null) 14 | %0 = call i1 @__quantum__qis__read_result__body(%Result* null) 15 | %1 = zext i1 %0 to i64 16 | %2 = mul i64 %1, 1 17 | %3 = or i64 %2, 0 18 | %4 = sub i64 1, %1 19 | %5 = mul i64 %4, 1 20 | %6 = xor i64 9223372036854775807, %5 21 | %7 = and i64 %6, %3 22 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) 23 | %8 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*)) 24 | %9 = zext i1 %8 to i64 25 | %10 = mul i64 %9, 2 26 | %11 = or i64 %10, %7 27 | %12 = sub i64 1, %9 28 | %13 = mul i64 %12, 2 29 | %14 = xor i64 9223372036854775807, %13 30 | %15 = and i64 %14, %11 31 | call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*)) 32 | %16 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 2 to %Result*)) 33 | %17 = zext i1 %16 to i64 34 | %18 = mul i64 %17, 4 35 | %19 = or i64 %18, %15 36 | %20 = sub i64 1, %17 37 | %21 = mul i64 %20, 4 38 | %22 = xor i64 9223372036854775807, %21 39 | %23 = and i64 %22, %19 40 | %24 = call i64 @add_one(i64 16106127360) 41 | call void @__quantum__rt__int_record_output(i64 %23, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @0, i32 0, i32 0)) 42 | call void @__quantum__rt__int_record_output(i64 16106127360, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @1, i32 0, i32 0)) 43 | call void @__quantum__rt__int_record_output(i64 %24, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @2, i32 0, i32 0)) 44 | ret void 45 | } 46 | 47 | declare i1 @__quantum__qis__read_result__body(%Result*) 48 | 49 | declare void @__quantum__rt__int_record_output(i64, i8*) 50 | 51 | declare void @init() #1 52 | 53 | declare i64 @add_one(i64) #1 54 | 55 | declare i64 @multi(i64, i64) #1 56 | 57 | declare i64 @add_two(i64) #1 58 | 59 | declare i64 @add_eleven(i64) #1 60 | 61 | declare void @no_return(i64) #1 62 | 63 | declare i64 @no_parameters() #1 64 | 65 | declare i64 @new_function() #1 66 | 67 | declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #2 68 | 69 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="6" "required_num_results"="6" } 70 | attributes #1 = { "wasm" } 71 | attributes #2 = { "irreversible" } 72 | 73 | !llvm.module.flags = !{!0, !1, !2, !3} 74 | 75 | !0 = !{i32 1, !"qir_major_version", i32 1} 76 | !1 = !{i32 7, !"qir_minor_version", i32 0} 77 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 78 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 79 | -------------------------------------------------------------------------------- /pytket/extensions/quantinuum/backends/hqslib1.inc: -------------------------------------------------------------------------------- 1 | // file: hqslib1.inc 2 | 3 | // --- Primitives --- 4 | 5 | // 1-qubit Z-axis rotation 6 | // NOTE: For HQS compiler / simulation, only one of these can be active at a time 7 | opaque Rz(lambda) q; 8 | //gate Rz(lambda) q 9 | //{ 10 | // U(0,0,lambda) q; 11 | //} 12 | 13 | // 1-qubit rotation gate 14 | // NOTE: For HQS compiler / simulation, only one of these can be active at a time 15 | opaque U1q(theta, phi) q; 16 | //gate U1q(theta, phi) q 17 | //{ 18 | // U(theta, phi, 0) q; 19 | //} 20 | 21 | // Unitary 2-qubit gate 22 | // NOTE: For HQS compiler / simulation, only one of these can be active at a time 23 | opaque ZZ() q1,q2; 24 | //gate ZZ() q1,q2 25 | //{ 26 | // U1q(pi/2, pi/2) q2; 27 | // CX q1, q2; 28 | // Rz(pi/2) q1; 29 | // U1q(pi/2, 0) q2; 30 | // U1q(pi/2, -pi/2) q2; 31 | //} 32 | 33 | // --- Standard Gates --- 34 | 35 | // Pauli gate: bit-flip 36 | gate x() a 37 | { 38 | U1q(pi, 0) a; 39 | } 40 | // Pauli gate: bit and phase flip 41 | gate y() a 42 | { 43 | U1q(pi, pi/2) a; 44 | } 45 | // Pauli gate: phase flip 46 | gate z() a 47 | { 48 | Rz(pi) a; 49 | } 50 | // Clifford gate: CNOT 51 | gate CX() c,t 52 | { 53 | U1q(-pi/2, pi/2) t; 54 | ZZ() c, t; 55 | Rz(-pi/2) c; 56 | U1q(pi/2, pi) t; 57 | Rz(-pi/2) t; 58 | } 59 | 60 | gate cx() c,t 61 | { 62 | CX c,t; 63 | } 64 | // Clifford gate: Hadamard 65 | gate h() a 66 | { 67 | U1q(pi/2, -pi/2) a; 68 | Rz(pi) a; 69 | } 70 | // Clifford gate: sqrt(Z) phase gate 71 | gate s() a 72 | { 73 | Rz(pi/2) a; 74 | } 75 | // Clifford gate: conjugate of sqrt(Z) 76 | gate sdg() a 77 | { 78 | Rz(-pi/2) a; 79 | } 80 | // C3 gate: sqrt(S) phase gate 81 | gate t() a 82 | { 83 | Rz(pi/4) a; 84 | } 85 | // C3 gate: conjugate of sqrt(S) 86 | gate tdg() a 87 | { 88 | Rz(-pi/4) a; 89 | } 90 | 91 | // --- Standard rotations --- 92 | 93 | // Rotation around X-axis 94 | gate rx(theta) a 95 | { 96 | U1q(theta, 0) a; 97 | } 98 | // Rotation around Y-axis 99 | gate ry(theta) a 100 | { 101 | U1q(theta, pi/2) a; 102 | } 103 | // Rotation around Z-axis 104 | gate rz(phi) a 105 | { 106 | Rz(phi) a; 107 | } 108 | 109 | // --- QE Standard User-Defined Gates --- 110 | 111 | // controlled-Phase 112 | gate cz() a,b 113 | { 114 | h b; 115 | cx a,b; 116 | h b; 117 | } 118 | // controlled-Y 119 | gate cy() a,b 120 | { 121 | sdg b; 122 | cx a,b; 123 | s b; 124 | } 125 | // controlled-H 126 | gate ch() a,b 127 | { 128 | h b; sdg b; 129 | cx a,b; 130 | h b; t b; 131 | cx a,b; 132 | t b; h b; s b; x b; s a; 133 | } 134 | // C3 gate: Toffoli 135 | gate ccx() a,b,c 136 | { 137 | h c; 138 | cx b,c; tdg c; 139 | cx a,c; t c; 140 | cx b,c; tdg c; 141 | cx a,c; t b; t c; h c; 142 | cx a,b; t a; tdg b; 143 | cx a,b; 144 | } 145 | // controlled rz rotation 146 | gate crz(lambda) a,b 147 | { 148 | Rz(lambda/2) b; 149 | cx a, b; 150 | Rz(-lambda/2) b; 151 | cx a, b; 152 | } 153 | // controlled phase rotation 154 | gate cu1(lambda) a,b 155 | { 156 | Rz(lambda/2) a; 157 | cx a, b; 158 | Rz(-lambda/2) b; 159 | cx a, b; 160 | Rz(lambda/2) b; 161 | } 162 | // controlled-U 163 | gate cu3(theta, phi, lambda) c, t 164 | { 165 | Rz((lambda-phi)/2) t; 166 | cx c, t; 167 | Rz(-(phi+lambda)/2) t; 168 | U1q(-theta/2, pi/2) t; 169 | cx c, t; 170 | U1q(theta/2, pi/2) t; 171 | Rz(phi) t; 172 | } 173 | -------------------------------------------------------------------------------- /tests/integration/qir/test_pytket_qir_6.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_pytket_qir_6' 2 | source_filename = "test_pytket_qir_6" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | @0 = internal constant [2 x i8] c"a\00" 8 | @1 = internal constant [2 x i8] c"b\00" 9 | @2 = internal constant [2 x i8] c"c\00" 10 | @3 = internal constant [2 x i8] c"d\00" 11 | 12 | define void @main() #0 { 13 | entry: 14 | %0 = call i1* @create_creg(i64 5) 15 | %1 = call i1* @create_creg(i64 5) 16 | %2 = call i1* @create_creg(i64 5) 17 | %3 = call i1* @create_creg(i64 5) 18 | %4 = call i64 @get_int_from_creg(i1* %0) 19 | %5 = call i64 @get_int_from_creg(i1* %1) 20 | %6 = or i64 %4, %5 21 | call void @set_creg_to_int(i1* %2, i64 %6) 22 | call void @__quantum__qis__x__body(%Qubit* null) 23 | call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*)) 24 | call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*)) 25 | call void @mz_to_creg_bit(%Qubit* null, i1* %2, i64 4) 26 | %7 = call i1 @get_creg_bit(i1* %2, i64 4) 27 | %8 = zext i1 %7 to i64 28 | call void @set_creg_to_int(i1* %2, i64 %8) 29 | br i1 %7, label %then, label %else 30 | 31 | then: ; preds = %entry 32 | call void @__quantum__qis__z__body(%Qubit* null) 33 | br label %continue 34 | 35 | else: ; preds = %entry 36 | br label %continue 37 | 38 | continue: ; preds = %else, %then 39 | call void @__quantum__qis__h__body(%Qubit* null) 40 | call void @__quantum__rt__tuple_start_record_output() 41 | %9 = call i64 @get_int_from_creg(i1* %0) 42 | call void @__quantum__rt__int_record_output(i64 %9, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @0, i32 0, i32 0)) 43 | %10 = call i64 @get_int_from_creg(i1* %1) 44 | call void @__quantum__rt__int_record_output(i64 %10, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @1, i32 0, i32 0)) 45 | %11 = call i64 @get_int_from_creg(i1* %2) 46 | call void @__quantum__rt__int_record_output(i64 %11, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @2, i32 0, i32 0)) 47 | %12 = call i64 @get_int_from_creg(i1* %3) 48 | call void @__quantum__rt__int_record_output(i64 %12, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @3, i32 0, i32 0)) 49 | call void @__quantum__rt__tuple_end_record_output() 50 | ret void 51 | } 52 | 53 | declare i1 @get_creg_bit(i1*, i64) 54 | 55 | declare void @set_creg_bit(i1*, i64, i1) 56 | 57 | declare void @set_creg_to_int(i1*, i64) 58 | 59 | declare i1 @__quantum__qis__read_result__body(%Result*) 60 | 61 | declare i1* @create_creg(i64) 62 | 63 | declare i64 @get_int_from_creg(i1*) 64 | 65 | declare void @mz_to_creg_bit(%Qubit*, i1*, i64) 66 | 67 | declare void @__quantum__rt__int_record_output(i64, i8*) 68 | 69 | declare void @__quantum__rt__tuple_start_record_output() 70 | 71 | declare void @__quantum__rt__tuple_end_record_output() 72 | 73 | declare void @__quantum__qis__x__body(%Qubit*) 74 | 75 | declare void @__quantum__qis__h__body(%Qubit*) 76 | 77 | declare void @__quantum__qis__z__body(%Qubit*) 78 | 79 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="3" "required_num_results"="3" } 80 | 81 | !llvm.module.flags = !{!0, !1, !2, !3} 82 | 83 | !0 = !{i32 1, !"qir_major_version", i32 1} 84 | !1 = !{i32 7, !"qir_minor_version", i32 0} 85 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 86 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pytket-quantinuum 2 | 3 | [![Slack](https://img.shields.io/badge/Slack-4A154B?style=for-the-badge&logo=slack&logoColor=white)](https://tketusers.slack.com/join/shared_invite/zt-18qmsamj9-UqQFVdkRzxnXCcKtcarLRA#) 4 | [![Stack Exchange](https://img.shields.io/badge/StackExchange-%23ffffff.svg?style=for-the-badge&logo=StackExchange)](https://quantumcomputing.stackexchange.com/tags/pytket) 5 | 6 | [Pytket](https://docs.quantinuum.com/tket/api-docs/index.html) is a python module for interfacing 7 | with tket, a quantum computing toolkit and optimising compiler developed by Quantinuum. 8 | 9 | `pytket-quantinuum` was an extension to `pytket` that allows `pytket` circuits to 10 | be executed on Quantinuum's quantum devices. As of version 0.56.0 it is now 11 | limited to compilation and local emulation. Please use the `qnexus` package for 12 | submission of jobs to devices. 13 | 14 | Some useful links: 15 | - [API Documentation](https://docs.quantinuum.com/tket/extensions/pytket-quantinuum/) 16 | 17 | ## Getting started 18 | 19 | `pytket-quantinuum` is available for Python 3.10, 3.11 and 3.12, on Linux, MacOS 20 | and Windows. To install, run: 21 | 22 | ```shell 23 | pip install pytket-quantinuum 24 | ``` 25 | 26 | This will install `pytket` if it isn't already installed, and add new classes 27 | and methods into the `pytket.extensions` namespace. 28 | 29 | ## Bugs, support and feature requests 30 | 31 | Please file bugs and feature requests on the Github 32 | [issue tracker](https://github.com/CQCL/pytket-quantinuum/issues). 33 | 34 | There is also a Slack channel for discussion and support. Click [here](https://tketusers.slack.com/join/shared_invite/zt-18qmsamj9-UqQFVdkRzxnXCcKtcarLRA#/shared-invite/email) to join. 35 | 36 | ## Development 37 | 38 | To install this extension in editable mode, simply change to this directory, and run: 39 | 40 | ```shell 41 | pip install -e . 42 | ``` 43 | ## Contributing 44 | 45 | Pull requests are welcome. To make a PR, first fork the repo, make your proposed 46 | changes on the `main` branch, and open a PR from your fork. If it passes 47 | tests and is accepted after review, it will be merged in. 48 | 49 | ### Code style 50 | 51 | #### Formatting 52 | 53 | All code should be formatted using 54 | [black](https://black.readthedocs.io/en/stable/), with default options. This is 55 | checked on the CI. The CI is currently using version 20.8b1. 56 | 57 | #### Type annotation 58 | 59 | On the CI, [mypy](https://mypy.readthedocs.io/en/stable/) is used as a static 60 | type checker and all submissions must pass its checks. You should therefore run 61 | `mypy` locally on any changed files before submitting a PR. Because of the way 62 | extension modules embed themselves into the `pytket` namespace this is a little 63 | complicated, but it should be sufficient to run the script `modules/mypy-check` 64 | (passing as a single argument the root directory of the module to test). The 65 | script requires `mypy` 0.800 or above. 66 | 67 | #### Linting 68 | 69 | We use [pylint](https://pypi.org/project/pylint/) on the CI to check compliance 70 | with a set of style requirements (listed in `.pylintrc`). You should run 71 | `pylint` over any changed files before submitting a PR, to catch any issues. 72 | 73 | ### Tests 74 | 75 | To run the tests: 76 | 77 | 1. `cd` into the `tests` directory; 78 | 2. ensure you have installed `pytest`, `hypothesis`, and any modules listed in 79 | the `test-requirements.txt` file (all via `pip`); 80 | 3. run `pytest`. 81 | 82 | When adding a new feature, please add a test for it. When fixing a bug, please 83 | add a test that demonstrates the fix. 84 | -------------------------------------------------------------------------------- /tests/wasm/collatz.wast: -------------------------------------------------------------------------------- 1 | (module 2 | (type (;0;) (func)) 3 | (type (;1;) (func (param i32) (result i32))) 4 | (func $__wasm_call_ctors (type 0)) 5 | (func $init (type 0) 6 | return) 7 | (func $collatz (type 1) (param i32) (result i32) 8 | (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) 9 | global.get $__stack_pointer 10 | local.set 1 11 | i32.const 16 12 | local.set 2 13 | local.get 1 14 | local.get 2 15 | i32.sub 16 | local.set 3 17 | local.get 3 18 | local.get 0 19 | i32.store offset=8 20 | local.get 3 21 | i32.load offset=8 22 | local.set 4 23 | block ;; label = @1 24 | block ;; label = @2 25 | local.get 4 26 | br_if 0 (;@2;) 27 | i32.const 0 28 | local.set 5 29 | local.get 3 30 | local.get 5 31 | i32.store offset=12 32 | br 1 (;@1;) 33 | end 34 | i32.const 0 35 | local.set 6 36 | local.get 3 37 | local.get 6 38 | i32.store offset=4 39 | block ;; label = @2 40 | loop ;; label = @3 41 | local.get 3 42 | i32.load offset=8 43 | local.set 7 44 | i32.const 1 45 | local.set 8 46 | local.get 7 47 | local.set 9 48 | local.get 8 49 | local.set 10 50 | local.get 9 51 | local.get 10 52 | i32.ne 53 | local.set 11 54 | i32.const 1 55 | local.set 12 56 | local.get 11 57 | local.get 12 58 | i32.and 59 | local.set 13 60 | local.get 13 61 | i32.eqz 62 | br_if 1 (;@2;) 63 | local.get 3 64 | i32.load offset=8 65 | local.set 14 66 | i32.const 1 67 | local.set 15 68 | local.get 14 69 | local.get 15 70 | i32.and 71 | local.set 16 72 | block ;; label = @4 73 | block ;; label = @5 74 | local.get 16 75 | i32.eqz 76 | br_if 0 (;@5;) 77 | local.get 3 78 | i32.load offset=8 79 | local.set 17 80 | i32.const 3 81 | local.set 18 82 | local.get 17 83 | local.get 18 84 | i32.mul 85 | local.set 19 86 | i32.const 1 87 | local.set 20 88 | local.get 19 89 | local.get 20 90 | i32.add 91 | local.set 21 92 | i32.const 1 93 | local.set 22 94 | local.get 21 95 | local.get 22 96 | i32.shr_u 97 | local.set 23 98 | local.get 3 99 | local.get 23 100 | i32.store offset=8 101 | br 1 (;@4;) 102 | end 103 | local.get 3 104 | i32.load offset=8 105 | local.set 24 106 | i32.const 1 107 | local.set 25 108 | local.get 24 109 | local.get 25 110 | i32.shr_u 111 | local.set 26 112 | local.get 3 113 | local.get 26 114 | i32.store offset=8 115 | end 116 | local.get 3 117 | i32.load offset=4 118 | local.set 27 119 | i32.const 1 120 | local.set 28 121 | local.get 27 122 | local.get 28 123 | i32.add 124 | local.set 29 125 | local.get 3 126 | local.get 29 127 | i32.store offset=4 128 | br 0 (;@3;) 129 | end 130 | end 131 | local.get 3 132 | i32.load offset=4 133 | local.set 30 134 | local.get 3 135 | local.get 30 136 | i32.store offset=12 137 | end 138 | local.get 3 139 | i32.load offset=12 140 | local.set 31 141 | local.get 31 142 | return) 143 | (memory (;0;) 2) 144 | (global $__stack_pointer (mut i32) (i32.const 66560)) 145 | (global (;1;) i32 (i32.const 1024)) 146 | (global (;2;) i32 (i32.const 1024)) 147 | (global (;3;) i32 (i32.const 1024)) 148 | (global (;4;) i32 (i32.const 66560)) 149 | (global (;5;) i32 (i32.const 0)) 150 | (global (;6;) i32 (i32.const 1)) 151 | (export "memory" (memory 0)) 152 | (export "__wasm_call_ctors" (func $__wasm_call_ctors)) 153 | (export "init" (func $init)) 154 | (export "collatz" (func $collatz)) 155 | (export "__dso_handle" (global 1)) 156 | (export "__data_end" (global 2)) 157 | (export "__global_base" (global 3)) 158 | (export "__heap_base" (global 4)) 159 | (export "__memory_base" (global 5)) 160 | (export "__table_base" (global 6))) 161 | -------------------------------------------------------------------------------- /tests/unit/offline_backend_test.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 collections import Counter 16 | 17 | import numpy as np 18 | import pytest 19 | from hypothesis import given, strategies 20 | from pytket.backends import CircuitNotValidError 21 | from pytket.circuit import Circuit 22 | from pytket.passes import ( 23 | FullPeepholeOptimise, 24 | OptimisePhaseGadgets, 25 | RemoveRedundancies, 26 | SequencePass, 27 | ) 28 | 29 | from pytket.extensions.quantinuum import Language, QuantinuumBackend 30 | from pytket.extensions.quantinuum.backends.api_wrappers import ( 31 | QuantinuumAPI, 32 | QuantinuumAPIOffline, 33 | ) 34 | 35 | 36 | def test_max_classical_register_ii() -> None: 37 | qapioffline = QuantinuumAPIOffline() 38 | backend = QuantinuumBackend( 39 | device_name="H2-1", 40 | machine_debug=False, 41 | api_handler=qapioffline, # type: ignore 42 | ) 43 | 44 | c = Circuit(4, 4, "test 1") 45 | c.H(0) 46 | c.CX(0, 1) 47 | c.measure_all() 48 | c = backend.get_compiled_circuit(c) 49 | assert backend._check_all_circuits([c]) # noqa: SLF001 50 | for i in range(20): 51 | c.add_c_register(f"creg-{i}", 32) 52 | 53 | assert backend._check_all_circuits([c]) # noqa: SLF001 54 | 55 | for i in range(20, 5000): 56 | c.add_c_register(f"creg-{i}", 32) 57 | 58 | with pytest.raises(CircuitNotValidError): 59 | backend._check_all_circuits([c]) # noqa: SLF001 60 | 61 | 62 | @pytest.mark.parametrize("language", [Language.QASM, Language.QIR, Language.PQIR]) 63 | def test_tket_pass_submission(language: Language) -> None: 64 | backend = QuantinuumBackend(device_name="H2-1SC", machine_debug=True) 65 | 66 | sequence_pass = SequencePass( 67 | [ 68 | OptimisePhaseGadgets(), 69 | FullPeepholeOptimise(), 70 | FullPeepholeOptimise(allow_swaps=False), 71 | RemoveRedundancies(), 72 | ] 73 | ) 74 | 75 | c = Circuit(4, 4, "test 1") 76 | c.H(0) 77 | c.measure_all() 78 | c = backend.get_compiled_circuit(c) 79 | n_shots = 4 80 | backend.process_circuits([c], n_shots, pytketpass=sequence_pass, language=language) 81 | 82 | 83 | @given( 84 | n_shots=strategies.integers(min_value=1, max_value=10), 85 | n_bits=strategies.integers(min_value=0, max_value=10), 86 | ) 87 | @pytest.mark.parametrize( 88 | "language", 89 | [ 90 | Language.QASM, 91 | Language.QIR, 92 | Language.PQIR, 93 | ], 94 | ) 95 | def test_shots_bits_edgecases(n_shots: int, n_bits: int, language: Language) -> None: 96 | quantinuum_backend = QuantinuumBackend("H2-1SC", machine_debug=True) 97 | c = Circuit(n_bits, n_bits) 98 | 99 | # TODO TKET-813 add more shot based backends and move to integration tests 100 | h = quantinuum_backend.process_circuit(c, n_shots, language=language) 101 | res = quantinuum_backend.get_result(h) 102 | 103 | correct_shots = np.zeros((n_shots, n_bits), dtype=int) 104 | correct_shape = (n_shots, n_bits) 105 | correct_counts = Counter({(0,) * n_bits: n_shots}) 106 | # BackendResult 107 | assert np.array_equal(res.get_shots(), correct_shots) 108 | assert res.get_shots().shape == correct_shape 109 | assert res.get_counts() == correct_counts 110 | 111 | # Direct 112 | res = quantinuum_backend.run_circuit(c, n_shots=n_shots, language=language) 113 | assert np.array_equal(res.get_shots(), correct_shots) 114 | assert res.get_shots().shape == correct_shape 115 | assert res.get_counts() == correct_counts 116 | 117 | 118 | def test_defaultapi_handler() -> None: 119 | """Test that the default API handler is used on backend construction.""" 120 | backend_1 = QuantinuumBackend("H2-1") 121 | backend_2 = QuantinuumBackend("H2-1") 122 | 123 | assert backend_1.api_handler is backend_2.api_handler 124 | 125 | 126 | def test_custom_api_handler() -> None: 127 | """Test that custom API handlers are used when used on backend construction.""" 128 | handler_1 = QuantinuumAPI() 129 | handler_2 = QuantinuumAPI() 130 | 131 | backend_1 = QuantinuumBackend("H2-1", api_handler=handler_1) 132 | backend_2 = QuantinuumBackend("H2-1", api_handler=handler_2) 133 | 134 | assert backend_1.api_handler is not backend_2.api_handler 135 | -------------------------------------------------------------------------------- /tests/integration/qir/test_pytket_qir_wasm_6-QIRProfile.PYTKET.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_pytket_qir_wasm_6-QIRProfile.PYTKET' 2 | source_filename = "test_pytket_qir_wasm_6-QIRProfile.PYTKET" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | @0 = internal constant [2 x i8] c"c\00" 8 | @1 = internal constant [3 x i8] c"c0\00" 9 | @2 = internal constant [3 x i8] c"c1\00" 10 | 11 | define void @main() #0 { 12 | entry: 13 | %0 = call i1* @create_creg(i64 6) 14 | %1 = call i1* @create_creg(i64 64) 15 | %2 = call i1* @create_creg(i64 32) 16 | call void @mz_to_creg_bit(%Qubit* null, i1* %0, i64 0) 17 | call void @mz_to_creg_bit(%Qubit* inttoptr (i64 1 to %Qubit*), i1* %0, i64 1) 18 | call void @mz_to_creg_bit(%Qubit* inttoptr (i64 2 to %Qubit*), i1* %0, i64 2) 19 | call void @set_creg_bit(i1* %1, i64 0, i1 false) 20 | call void @set_creg_bit(i1* %1, i64 1, i1 false) 21 | call void @set_creg_bit(i1* %1, i64 2, i1 false) 22 | call void @set_creg_bit(i1* %1, i64 3, i1 false) 23 | call void @set_creg_bit(i1* %1, i64 4, i1 false) 24 | call void @set_creg_bit(i1* %1, i64 5, i1 false) 25 | call void @set_creg_bit(i1* %1, i64 6, i1 false) 26 | call void @set_creg_bit(i1* %1, i64 7, i1 false) 27 | call void @set_creg_bit(i1* %1, i64 8, i1 false) 28 | call void @set_creg_bit(i1* %1, i64 9, i1 false) 29 | call void @set_creg_bit(i1* %1, i64 10, i1 false) 30 | call void @set_creg_bit(i1* %1, i64 11, i1 false) 31 | call void @set_creg_bit(i1* %1, i64 12, i1 false) 32 | call void @set_creg_bit(i1* %1, i64 13, i1 false) 33 | call void @set_creg_bit(i1* %1, i64 14, i1 false) 34 | call void @set_creg_bit(i1* %1, i64 15, i1 false) 35 | call void @set_creg_bit(i1* %1, i64 16, i1 false) 36 | call void @set_creg_bit(i1* %1, i64 17, i1 false) 37 | call void @set_creg_bit(i1* %1, i64 18, i1 false) 38 | call void @set_creg_bit(i1* %1, i64 19, i1 false) 39 | call void @set_creg_bit(i1* %1, i64 20, i1 false) 40 | call void @set_creg_bit(i1* %1, i64 21, i1 false) 41 | call void @set_creg_bit(i1* %1, i64 22, i1 false) 42 | call void @set_creg_bit(i1* %1, i64 23, i1 false) 43 | call void @set_creg_bit(i1* %1, i64 24, i1 false) 44 | call void @set_creg_bit(i1* %1, i64 25, i1 false) 45 | call void @set_creg_bit(i1* %1, i64 26, i1 false) 46 | call void @set_creg_bit(i1* %1, i64 27, i1 false) 47 | call void @set_creg_bit(i1* %1, i64 28, i1 false) 48 | call void @set_creg_bit(i1* %1, i64 29, i1 false) 49 | call void @set_creg_bit(i1* %1, i64 30, i1 true) 50 | call void @set_creg_bit(i1* %1, i64 31, i1 true) 51 | call void @set_creg_bit(i1* %1, i64 32, i1 true) 52 | call void @set_creg_bit(i1* %1, i64 33, i1 true) 53 | call void @set_creg_bit(i1* %1, i64 34, i1 false) 54 | call void @set_creg_bit(i1* %1, i64 35, i1 false) 55 | call void @set_creg_bit(i1* %1, i64 36, i1 false) 56 | call void @set_creg_bit(i1* %1, i64 37, i1 false) 57 | call void @set_creg_bit(i1* %1, i64 38, i1 false) 58 | call void @set_creg_bit(i1* %1, i64 39, i1 false) 59 | call void @set_creg_bit(i1* %1, i64 40, i1 false) 60 | call void @set_creg_bit(i1* %1, i64 41, i1 false) 61 | call void @set_creg_bit(i1* %1, i64 42, i1 false) 62 | call void @set_creg_bit(i1* %1, i64 43, i1 false) 63 | call void @set_creg_bit(i1* %1, i64 44, i1 false) 64 | call void @set_creg_bit(i1* %1, i64 45, i1 false) 65 | call void @set_creg_bit(i1* %1, i64 46, i1 false) 66 | call void @set_creg_bit(i1* %1, i64 47, i1 false) 67 | call void @set_creg_bit(i1* %1, i64 48, i1 false) 68 | call void @set_creg_bit(i1* %1, i64 49, i1 false) 69 | call void @set_creg_bit(i1* %1, i64 50, i1 false) 70 | call void @set_creg_bit(i1* %1, i64 51, i1 false) 71 | call void @set_creg_bit(i1* %1, i64 52, i1 false) 72 | call void @set_creg_bit(i1* %1, i64 53, i1 false) 73 | call void @set_creg_bit(i1* %1, i64 54, i1 false) 74 | call void @set_creg_bit(i1* %1, i64 55, i1 false) 75 | call void @set_creg_bit(i1* %1, i64 56, i1 false) 76 | call void @set_creg_bit(i1* %1, i64 57, i1 false) 77 | call void @set_creg_bit(i1* %1, i64 58, i1 false) 78 | call void @set_creg_bit(i1* %1, i64 59, i1 false) 79 | call void @set_creg_bit(i1* %1, i64 60, i1 false) 80 | call void @set_creg_bit(i1* %1, i64 61, i1 false) 81 | call void @set_creg_bit(i1* %1, i64 62, i1 false) 82 | call void @set_creg_bit(i1* %1, i64 63, i1 false) 83 | %3 = call i64 @get_int_from_creg(i1* %1) 84 | %4 = call i64 @add_one(i64 %3) 85 | call void @set_creg_to_int(i1* %2, i64 %4) 86 | %5 = call i64 @get_int_from_creg(i1* %0) 87 | call void @__quantum__rt__int_record_output(i64 %5, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @0, i32 0, i32 0)) 88 | %6 = call i64 @get_int_from_creg(i1* %1) 89 | call void @__quantum__rt__int_record_output(i64 %6, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @1, i32 0, i32 0)) 90 | %7 = call i64 @get_int_from_creg(i1* %2) 91 | call void @__quantum__rt__int_record_output(i64 %7, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @2, i32 0, i32 0)) 92 | ret void 93 | } 94 | 95 | declare i1 @__quantum__qis__read_result__body(%Result*) 96 | 97 | declare void @__quantum__rt__int_record_output(i64, i8*) 98 | 99 | declare void @init() #1 100 | 101 | declare i64 @add_one(i64) #1 102 | 103 | declare i64 @multi(i64, i64) #1 104 | 105 | declare i64 @add_two(i64) #1 106 | 107 | declare i64 @add_eleven(i64) #1 108 | 109 | declare void @no_return(i64) #1 110 | 111 | declare i64 @no_parameters() #1 112 | 113 | declare i64 @new_function() #1 114 | 115 | declare i1 @get_creg_bit(i1*, i64) 116 | 117 | declare void @set_creg_bit(i1*, i64, i1) 118 | 119 | declare void @set_creg_to_int(i1*, i64) 120 | 121 | declare i1* @create_creg(i64) 122 | 123 | declare i64 @get_int_from_creg(i1*) 124 | 125 | declare void @mz_to_creg_bit(%Qubit*, i1*, i64) 126 | 127 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="6" "required_num_results"="6" } 128 | 129 | attributes #1 = { "wasm" } 130 | 131 | !llvm.module.flags = !{!0, !1, !2, !3} 132 | 133 | !0 = !{i32 1, !"qir_major_version", i32 1} 134 | !1 = !{i32 7, !"qir_minor_version", i32 0} 135 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 136 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 137 | -------------------------------------------------------------------------------- /tests/integration/qir/test_pytket_qir_wasm_5-QIRProfile.PYTKET.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'test_pytket_qir_wasm_5-QIRProfile.PYTKET' 2 | source_filename = "test_pytket_qir_wasm_5-QIRProfile.PYTKET" 3 | 4 | %Qubit = type opaque 5 | %Result = type opaque 6 | 7 | @0 = internal constant [2 x i8] c"c\00" 8 | @1 = internal constant [3 x i8] c"c0\00" 9 | @2 = internal constant [3 x i8] c"c1\00" 10 | 11 | define void @main() #0 { 12 | entry: 13 | %0 = call i1* @create_creg(i64 6) 14 | %1 = call i1* @create_creg(i64 64) 15 | %2 = call i1* @create_creg(i64 32) 16 | call void @mz_to_creg_bit(%Qubit* null, i1* %0, i64 0) 17 | call void @mz_to_creg_bit(%Qubit* inttoptr (i64 1 to %Qubit*), i1* %0, i64 1) 18 | call void @mz_to_creg_bit(%Qubit* inttoptr (i64 2 to %Qubit*), i1* %0, i64 2) 19 | call void @set_creg_bit(i1* %1, i64 0, i1 true) 20 | call void @set_creg_bit(i1* %1, i64 1, i1 false) 21 | call void @set_creg_bit(i1* %1, i64 2, i1 false) 22 | call void @set_creg_bit(i1* %1, i64 3, i1 false) 23 | call void @set_creg_bit(i1* %1, i64 4, i1 false) 24 | call void @set_creg_bit(i1* %1, i64 5, i1 false) 25 | call void @set_creg_bit(i1* %1, i64 6, i1 false) 26 | call void @set_creg_bit(i1* %1, i64 7, i1 false) 27 | call void @set_creg_bit(i1* %1, i64 8, i1 false) 28 | call void @set_creg_bit(i1* %1, i64 9, i1 false) 29 | call void @set_creg_bit(i1* %1, i64 10, i1 false) 30 | call void @set_creg_bit(i1* %1, i64 11, i1 false) 31 | call void @set_creg_bit(i1* %1, i64 12, i1 false) 32 | call void @set_creg_bit(i1* %1, i64 13, i1 false) 33 | call void @set_creg_bit(i1* %1, i64 14, i1 false) 34 | call void @set_creg_bit(i1* %1, i64 15, i1 false) 35 | call void @set_creg_bit(i1* %1, i64 16, i1 false) 36 | call void @set_creg_bit(i1* %1, i64 17, i1 false) 37 | call void @set_creg_bit(i1* %1, i64 18, i1 false) 38 | call void @set_creg_bit(i1* %1, i64 19, i1 false) 39 | call void @set_creg_bit(i1* %1, i64 20, i1 false) 40 | call void @set_creg_bit(i1* %1, i64 21, i1 false) 41 | call void @set_creg_bit(i1* %1, i64 22, i1 false) 42 | call void @set_creg_bit(i1* %1, i64 23, i1 false) 43 | call void @set_creg_bit(i1* %1, i64 24, i1 false) 44 | call void @set_creg_bit(i1* %1, i64 25, i1 false) 45 | call void @set_creg_bit(i1* %1, i64 26, i1 false) 46 | call void @set_creg_bit(i1* %1, i64 27, i1 false) 47 | call void @set_creg_bit(i1* %1, i64 28, i1 false) 48 | call void @set_creg_bit(i1* %1, i64 29, i1 false) 49 | call void @set_creg_bit(i1* %1, i64 30, i1 false) 50 | call void @set_creg_bit(i1* %1, i64 31, i1 false) 51 | call void @set_creg_bit(i1* %1, i64 32, i1 false) 52 | call void @set_creg_bit(i1* %1, i64 33, i1 false) 53 | call void @set_creg_bit(i1* %1, i64 34, i1 false) 54 | call void @set_creg_bit(i1* %1, i64 35, i1 false) 55 | call void @set_creg_bit(i1* %1, i64 36, i1 false) 56 | call void @set_creg_bit(i1* %1, i64 37, i1 false) 57 | call void @set_creg_bit(i1* %1, i64 38, i1 false) 58 | call void @set_creg_bit(i1* %1, i64 39, i1 false) 59 | call void @set_creg_bit(i1* %1, i64 40, i1 false) 60 | call void @set_creg_bit(i1* %1, i64 41, i1 false) 61 | call void @set_creg_bit(i1* %1, i64 42, i1 false) 62 | call void @set_creg_bit(i1* %1, i64 43, i1 false) 63 | call void @set_creg_bit(i1* %1, i64 44, i1 false) 64 | call void @set_creg_bit(i1* %1, i64 45, i1 false) 65 | call void @set_creg_bit(i1* %1, i64 46, i1 false) 66 | call void @set_creg_bit(i1* %1, i64 47, i1 false) 67 | call void @set_creg_bit(i1* %1, i64 48, i1 false) 68 | call void @set_creg_bit(i1* %1, i64 49, i1 false) 69 | call void @set_creg_bit(i1* %1, i64 50, i1 false) 70 | call void @set_creg_bit(i1* %1, i64 51, i1 false) 71 | call void @set_creg_bit(i1* %1, i64 52, i1 false) 72 | call void @set_creg_bit(i1* %1, i64 53, i1 false) 73 | call void @set_creg_bit(i1* %1, i64 54, i1 false) 74 | call void @set_creg_bit(i1* %1, i64 55, i1 false) 75 | call void @set_creg_bit(i1* %1, i64 56, i1 false) 76 | call void @set_creg_bit(i1* %1, i64 57, i1 false) 77 | call void @set_creg_bit(i1* %1, i64 58, i1 false) 78 | call void @set_creg_bit(i1* %1, i64 59, i1 false) 79 | call void @set_creg_bit(i1* %1, i64 60, i1 false) 80 | call void @set_creg_bit(i1* %1, i64 61, i1 false) 81 | call void @set_creg_bit(i1* %1, i64 62, i1 false) 82 | call void @set_creg_bit(i1* %1, i64 63, i1 false) 83 | %3 = call i64 @get_int_from_creg(i1* %1) 84 | %4 = call i64 @add_one(i64 %3) 85 | call void @set_creg_to_int(i1* %2, i64 %4) 86 | %5 = call i64 @get_int_from_creg(i1* %0) 87 | call void @__quantum__rt__int_record_output(i64 %5, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @0, i32 0, i32 0)) 88 | %6 = call i64 @get_int_from_creg(i1* %1) 89 | call void @__quantum__rt__int_record_output(i64 %6, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @1, i32 0, i32 0)) 90 | %7 = call i64 @get_int_from_creg(i1* %2) 91 | call void @__quantum__rt__int_record_output(i64 %7, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @2, i32 0, i32 0)) 92 | ret void 93 | } 94 | 95 | declare i1 @__quantum__qis__read_result__body(%Result*) 96 | 97 | declare void @__quantum__rt__int_record_output(i64, i8*) 98 | 99 | declare void @init() #1 100 | 101 | declare i64 @add_one(i64) #1 102 | 103 | declare i64 @multi(i64, i64) #1 104 | 105 | declare i64 @add_two(i64) #1 106 | 107 | declare i64 @add_eleven(i64) #1 108 | 109 | declare void @no_return(i64) #1 110 | 111 | declare i64 @no_parameters() #1 112 | 113 | declare i64 @new_function() #1 114 | 115 | declare i1 @get_creg_bit(i1*, i64) 116 | 117 | declare void @set_creg_bit(i1*, i64, i1) 118 | 119 | declare void @set_creg_to_int(i1*, i64) 120 | 121 | declare i1* @create_creg(i64) 122 | 123 | declare i64 @get_int_from_creg(i1*) 124 | 125 | declare void @mz_to_creg_bit(%Qubit*, i1*, i64) 126 | 127 | attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="6" "required_num_results"="6" } 128 | 129 | attributes #1 = { "wasm" } 130 | 131 | !llvm.module.flags = !{!0, !1, !2, !3} 132 | 133 | !0 = !{i32 1, !"qir_major_version", i32 1} 134 | !1 = !{i32 7, !"qir_minor_version", i32 0} 135 | !2 = !{i32 1, !"dynamic_qubit_management", i1 false} 136 | !3 = !{i32 1, !"dynamic_result_management", i1 false} 137 | -------------------------------------------------------------------------------- /pytket/extensions/quantinuum/backends/api_wrappers.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 Any 16 | 17 | 18 | class QuantinuumAPIError(Exception): 19 | pass 20 | 21 | 22 | OFFLINE_MACHINE_LIST = [ 23 | { 24 | "wasm": True, 25 | "batching": True, 26 | "supported_languages": ["OPENQASM 2.0", "QIR 1.0"], 27 | "benchmarks": {"qv": {"date": "2024-04-04", "value": 1048576.0}}, 28 | "max_classical_register_width": 63, 29 | "gateset": ["RZZ", "Rxxyyzz", "Rz", "U1q", "ZZ"], 30 | "name": "H1-1", 31 | "syntax_checker": "H1-1SC", 32 | "n_gate_zones": "5", 33 | "noise_specs": { 34 | "date": "2025-05-02", 35 | "spam_error": { 36 | "p_meas_1_unc": 0.000176, 37 | "p_meas_0": 0.00122, 38 | "p_meas_1": 0.00343, 39 | "p_meas_0_unc": 0.000105, 40 | }, 41 | "crosstalk_error": { 42 | "p_crosstalk_meas_unc": 1.7e-06, 43 | "p_crosstalk_meas": 4.1e-05, 44 | }, 45 | "memory_error": { 46 | "memory_error_unc": 9.16e-05, 47 | "memory_error": 0.000222, 48 | }, 49 | "1q_gate_error": {"p1": 1.8e-05, "p1_unc": 2.93e-06}, 50 | "2q_gate_error": {"p2_unc": 6.23e-05, "p2": 0.000973}, 51 | }, 52 | "max_n_shots": 10000, 53 | "n_qubits": 20, 54 | "n_classical_registers": 4000, 55 | "system_type": "hardware", 56 | "connectivity": "all-to-all", 57 | "emulator": "H1-1E", 58 | }, 59 | { 60 | "wasm": True, 61 | "batching": True, 62 | "supported_languages": ["OPENQASM 2.0", "QIR 1.0"], 63 | "benchmarks": {"qv": {"date": "2024-08-11", "value": 2097152.0}}, 64 | "max_classical_register_width": 63, 65 | "gateset": ["RZZ", "Rxxyyzz", "Rz", "U1q", "ZZ"], 66 | "name": "H2-1", 67 | "syntax_checker": "H2-1SC", 68 | "n_gate_zones": "4", 69 | "noise_specs": { 70 | "date": "2025-04-30", 71 | "spam_error": { 72 | "p_meas_1_unc": 0.000124, 73 | "p_meas_0": 0.0006, 74 | "p_meas_1": 0.00139, 75 | "p_meas_0_unc": 8.16e-05, 76 | }, 77 | "crosstalk_error": { 78 | "p_crosstalk_meas_unc": 8.98e-07, 79 | "p_crosstalk_meas": 6.65e-06, 80 | }, 81 | "memory_error": { 82 | "memory_error_unc": 2.34e-05, 83 | "memory_error": 0.000203, 84 | }, 85 | "1q_gate_error": {"p1": 1.89e-05, "p1_unc": 4.23e-06}, 86 | "2q_gate_error": {"p2_unc": 8.08e-05, "p2": 0.00105}, 87 | }, 88 | "max_n_shots": 10000, 89 | "n_qubits": 56, 90 | "n_classical_registers": 4000, 91 | "system_type": "hardware", 92 | "connectivity": "all-to-all", 93 | "emulator": "H2-1E", 94 | }, 95 | { 96 | "wasm": True, 97 | "batching": True, 98 | "supported_languages": ["OPENQASM 2.0", "QIR 1.0"], 99 | "benchmarks": {"qv": {"date": "2025-09-08", "value": 33554432.0}}, 100 | "max_classical_register_width": 63, 101 | "gateset": ["RZZ", "Rxxyyzz", "Rz", "U1q", "ZZ"], 102 | "name": "H2-2", 103 | "syntax_checker": "H2-2SC", 104 | "n_gate_zones": "4", 105 | "noise_specs": { 106 | "date": "2025-08-28", 107 | "spam_error": { 108 | "p_meas_1_unc": 0.00011, 109 | "p_meas_0": 0.00067, 110 | "p_meas_1": 0.0012, 111 | "p_meas_0_unc": 8.7e-05, 112 | }, 113 | "crosstalk_error": { 114 | "p_crosstalk_meas_unc": 5.3e-07, 115 | "p_crosstalk_meas": 2.2e-05, 116 | }, 117 | "memory_error": {"memory_error_unc": 2e-05, "memory_error": 0.00012}, 118 | "1q_gate_error": {"p1": 2.8e-05, "p1_unc": 3.6e-06}, 119 | "2q_gate_error": {"p2_unc": 4.8e-05, "p2": 0.00083}, 120 | }, 121 | "max_n_shots": 10000, 122 | "n_qubits": 56, 123 | "n_classical_registers": 4000, 124 | "system_type": "hardware", 125 | "connectivity": "all-to-all", 126 | "emulator": "H2-2E", 127 | }, 128 | ] 129 | 130 | 131 | class QuantinuumAPIOffline: 132 | """ 133 | Offline Quantinuum API emulator. 134 | """ 135 | 136 | def __init__(self, machine_list: list | None = None): 137 | """Initialize offline API client. 138 | 139 | All jobs that are submitted to this offline API are stored 140 | and can be requested again later. 141 | 142 | :param machine_list: List of dictionaries each containing device information. 143 | One short example: 144 | { 145 | "name": "H1-1", 146 | "n_qubits": 20, 147 | "gateset": ["RZZ", "Riswap", "TK2"], 148 | "n_shots": 10000, 149 | "batching": True, 150 | } 151 | """ 152 | if machine_list is None: 153 | machine_list = OFFLINE_MACHINE_LIST 154 | self.machine_list = machine_list 155 | self.submitted: list = [] 156 | 157 | def get_machine_list(self) -> list[dict[str, Any]]: 158 | """Returns a given list of the available machines 159 | :return: list of machines 160 | """ 161 | 162 | return self.machine_list 163 | 164 | def _submit_job(self, body: dict) -> None: 165 | """The function will take the submitted job and store it for later 166 | 167 | :param body: submitted job 168 | 169 | :return: None 170 | """ 171 | self.submitted.append(body) 172 | 173 | def get_jobs(self) -> list | None: 174 | """The function will return all the jobs that have been submitted 175 | 176 | :return: List of all the submitted jobs 177 | """ 178 | return self.submitted 179 | 180 | 181 | class QuantinuumAPI(QuantinuumAPIOffline): 182 | """ 183 | Alias for `QuantinuumAPIOffline`. 184 | """ 185 | -------------------------------------------------------------------------------- /pytket/extensions/quantinuum/backends/leakage_gadget.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 collections import Counter 16 | from typing import TYPE_CHECKING, cast 17 | 18 | from pytket.backends.backendresult import BackendResult 19 | from pytket.utils.outcomearray import OutcomeArray 20 | 21 | from pytket import Bit, Circuit, OpType, Qubit # type: ignore 22 | 23 | if TYPE_CHECKING: 24 | from collections.abc import Sequence 25 | 26 | LEAKAGE_DETECTION_BIT_NAME_ = "leakage_detection_bit" 27 | LEAKAGE_DETECTION_QUBIT_NAME_ = "leakage_detection_qubit" 28 | 29 | 30 | def get_leakage_gadget_circuit( 31 | circuit_qubit: Qubit, postselection_qubit: Qubit, postselection_bit: Bit 32 | ) -> Circuit: 33 | """ 34 | Returns a two qubit Circuit for detecting leakage errors. 35 | 36 | :param circuit_qubit: Generated circuit detects whether leakage errors 37 | have occurred in this qubit. 38 | :param postselection_qubit: Measured qubit to detect leakage error. 39 | :param postselection_bit: Leakage detection result is written to this bit. 40 | :return: Circuit for detecting leakage errors for specified ids. 41 | """ 42 | c = Circuit() 43 | c.add_qubit(circuit_qubit) 44 | c.add_qubit(postselection_qubit) 45 | c.add_gate(OpType.Reset, [postselection_qubit]) 46 | c.add_bit(postselection_bit) 47 | c.X(postselection_qubit) 48 | c.add_barrier([circuit_qubit, postselection_qubit]) 49 | c.H(postselection_qubit).ZZMax(postselection_qubit, circuit_qubit) 50 | c.add_barrier([circuit_qubit, postselection_qubit]) 51 | c.ZZMax(postselection_qubit, circuit_qubit).H(postselection_qubit).Z(circuit_qubit) 52 | c.add_barrier([circuit_qubit, postselection_qubit]) 53 | c.Measure(postselection_qubit, postselection_bit) 54 | return c 55 | 56 | 57 | def get_detection_circuit(circuit: Circuit, n_device_qubits: int) -> Circuit: # noqa: PLR0912 58 | """ 59 | For a passed circuit, appends a leakage detection circuit for 60 | each end of circuit measurement using spare device qubits. 61 | All additional Qubit added for leakage detection are 62 | written to a new register "leakage_detection_qubit" and all 63 | additional Bit are written to a new register "leakage_detection_bit". 64 | 65 | :param circuit: Circuit to have leakage detection added. 66 | :param n_device_qubits: Total number of qubits supported by the device 67 | being compiled to. 68 | 69 | :return: Circuit with leakage detection circuitry added. 70 | """ 71 | n_qubits: int = circuit.n_qubits 72 | if n_qubits == 0: 73 | raise ValueError( 74 | "Circuit for Leakage Gadget Postselection must have at least one Qubit." 75 | ) 76 | n_spare_qubits: int = n_device_qubits - n_qubits 77 | # N.b. even if n_spare_qubits == 0 , we will reuse measured data qubits 78 | 79 | # construct detection circuit 80 | detection_circuit: Circuit = Circuit() 81 | postselection_qubits: list[Qubit] = [ 82 | Qubit(LEAKAGE_DETECTION_QUBIT_NAME_, i) for i in range(n_spare_qubits) 83 | ] 84 | for q in circuit.qubits + postselection_qubits: 85 | detection_circuit.add_qubit(q) 86 | for b in circuit.bits: 87 | detection_circuit.add_bit(b) 88 | 89 | # construct a Circuit that is the original Circuit without 90 | # end of Circuit Measure gates 91 | end_circuit_measures: dict[Qubit, Bit] = {} 92 | for com in circuit: 93 | if com.op.type == OpType.Barrier: 94 | detection_circuit.add_barrier(com.args) 95 | continue 96 | # first check if a mid circuit measure needs to be readded 97 | for q in com.qubits: 98 | # this condition only true if this Qubit has previously had a 99 | # "mid-circuit" measure operation 100 | if q in end_circuit_measures: 101 | detection_circuit.Measure(q, end_circuit_measures.pop(q)) 102 | if com.op.type == OpType.Measure: 103 | # if this is "mid-circuit" then this will be rewritten later 104 | end_circuit_measures[com.qubits[0]] = com.bits[0] 105 | elif com.op.params: 106 | detection_circuit.add_gate(com.op.type, com.op.params, com.args) 107 | else: 108 | detection_circuit.add_gate(com.op.type, com.args) 109 | 110 | # for each entry in end_circuit_measures, we want to add a leakage_gadget_circuit 111 | # we try to use each free architecture qubit as few times as possible 112 | ps_q_index: int = 0 113 | 114 | # if there are no spare qubits we measure the first qubit and then use it as 115 | # an ancilla qubit for leakage detection 116 | if not postselection_qubits: 117 | qb: Qubit = next(iter(end_circuit_measures)) 118 | bb: Bit = end_circuit_measures.pop(qb) 119 | detection_circuit.Measure(qb, bb) 120 | postselection_qubits.append(qb) 121 | 122 | for ps_b_index, q in enumerate(end_circuit_measures): 123 | if q.reg_name == LEAKAGE_DETECTION_QUBIT_NAME_: 124 | raise ValueError( 125 | "Leakage Gadget scheme makes a qubit register named " 126 | "'leakage_detection_qubit' but this already exists in" 127 | " the passed circuit." 128 | ) 129 | ps_q_index = 0 if ps_q_index == len(postselection_qubits) else ps_q_index 130 | leakage_detection_bit: Bit = Bit(LEAKAGE_DETECTION_BIT_NAME_, ps_b_index) 131 | if leakage_detection_bit in circuit.bits: 132 | raise ValueError( 133 | "Leakage Gadget scheme makes a new Bit named 'leakage_detection_bit'" 134 | " but this already exists in the passed circuit." 135 | ) 136 | leakage_gadget_circuit: Circuit = get_leakage_gadget_circuit( 137 | q, postselection_qubits[ps_q_index], leakage_detection_bit 138 | ) 139 | detection_circuit.append(leakage_gadget_circuit) 140 | # increment value for adding postselection to 141 | ps_q_index += 1 142 | 143 | detection_circuit.Measure(q, end_circuit_measures[q]) 144 | 145 | # we can now add this qubit to the set of qubits used for postselection 146 | postselection_qubits.append(q) 147 | 148 | detection_circuit.remove_blank_wires() 149 | return detection_circuit 150 | 151 | 152 | def prune_shots_detected_as_leaky(result: BackendResult) -> BackendResult: 153 | """ 154 | For all states with a Bit with name "leakage_detection_bit" 155 | in a state 1 sets the counts to 0. 156 | 157 | :param result: Shots returned from device. 158 | :return: Shots with leakage cases removed. 159 | """ 160 | regular_bits: list[Bit] = [ 161 | b for b in result.c_bits if b.reg_name != LEAKAGE_DETECTION_BIT_NAME_ 162 | ] 163 | leakage_bits: list[Bit] = [ 164 | b for b in result.c_bits if b.reg_name == LEAKAGE_DETECTION_BIT_NAME_ 165 | ] 166 | received_counts: Counter[tuple[int, ...]] = result.get_counts( 167 | cbits=regular_bits + leakage_bits 168 | ) 169 | discarded_counts: Counter[tuple[int, ...]] = Counter( 170 | { 171 | tuple(state[: len(regular_bits)]): received_counts[state] 172 | for state in received_counts 173 | if not any(state[-len(leakage_bits) :]) 174 | } 175 | ) 176 | return BackendResult( 177 | counts=Counter( 178 | { 179 | OutcomeArray.from_readouts([key]): val 180 | for key, val in discarded_counts.items() 181 | } 182 | ), 183 | c_bits=cast("Sequence[Bit]", regular_bits), 184 | ) 185 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | file_format: mystnb 3 | --- 4 | 5 | # pytket-quantinuum 6 | 7 | `pytket-quantinuum` is an extension to `pytket` that allows `pytket` circuits to 8 | be compiled for execution on Quantinuum's quantum devices. As of version 0.56.0 9 | it no longer allows submission to Quantinuum devices: please use the `qnexus` 10 | package for that. 11 | 12 | `pytket-quantinuum` is available for Python 3.10, 3.11 and 3.12, on Linux, MacOS 13 | and Windows. To install, run: 14 | 15 | ``` 16 | pip install pytket-quantinuum 17 | ``` 18 | 19 | # Available Backends 20 | 21 | The pytket-quantinuum extension allows the user to access the following quantum emulators. These backends can be initialised by passing the device name as a string to the {py:class}`~.QuantinuumBackend` class. The available devices are: 22 | 23 | - `H2-1LE`, a version of the `H2-1E` emulator that runs locally. For running simulations locally, see the docs on [Local Emulators](#local-emulators). 24 | 25 | # Default Compilation 26 | 27 | Every {py:class}`~pytket.backends.backend.Backend` in pytket has its own {py:meth}`~pytket.backends.backend.Backend.default_compilation_pass` method. 28 | This method applies a sequence of optimisations to a circuit depending on the value of an `optimisation_level` parameter. 29 | This default compilation will ensure that the circuit meets all the constraints required to run on the {py:class}`~pytket.backends.backend.Backend`. 30 | 31 | The default pass can be applied in place as follows 32 | 33 | ```{code-cell} ipython3 34 | --- 35 | tags: [skip-execution] 36 | --- 37 | from pytket import Circuit 38 | from pytket.extensions.quantinuum import QuantinuumBackend 39 | 40 | circ = Circuit(2).H(0).CX(0, 1).CZ(0, 1) 41 | backend = QuantinuumBackend('H2-1E') 42 | 43 | # Compile the circuit in place. The optimisation level is set to 2 by default. 44 | backend.default_compilation_pass().apply(circ) 45 | ``` 46 | 47 | Alternatively the default pass can be applied using the {py:meth}`~pytket.backends.backend.Backend.get_compiled_circuit` method. 48 | 49 | ```{code-cell} ipython3 50 | --- 51 | tags: [skip-execution] 52 | --- 53 | compiled_circ = backend.get_compiled_circuit(circ) 54 | ``` 55 | 56 | The passes applied by different levels of optimisation are specified in the table below. Note that optimisation level 0, 1 and 2 do not remove barriers from 57 | a circuit, while optimisation level 3 will. At optimisation level 3 the default timeout is 5 minutes - consider increasing this for larger circuits if 58 | the circuit 2-qubit gate count is not reduced after compilation. 59 | 60 | :::{list-table} **Default compilation pass for the QuantinuumBackend** 61 | :widths: 25 25 25 25 62 | :header-rows: 1 63 | 64 | * - optimisation_level = 0 65 | - optimisation_level = 1 66 | - optimisation_level = 2 [1] 67 | - optimisation_level = 3 68 | * - {py:meth}`~pytket.passes.DecomposeBoxes` 69 | - {py:meth}`~pytket.passes.DecomposeBoxes` 70 | - {py:meth}`~pytket.passes.DecomposeBoxes` 71 | - {py:meth}`~pytket.passes.DecomposeBoxes` 72 | * - {py:func}`~pytket.passes.resizeregpass.scratch_reg_resize_pass` 73 | - {py:func}`~pytket.passes.resizeregpass.scratch_reg_resize_pass` 74 | - {py:func}`~pytket.passes.resizeregpass.scratch_reg_resize_pass` 75 | - {py:func}`~pytket.passes.resizeregpass.scratch_reg_resize_pass` 76 | * - {py:meth}`~pytket.passes.AutoRebase` [2] 77 | - {py:meth}`~pytket.passes.SynthesiseTket` 78 | - {py:meth}`~pytket.passes.FullPeepholeOptimise` [3] 79 | - {py:meth}`~pytket.passes.RemoveBarriers` 80 | * - {py:meth}`~pytket.passes.FlattenRelabelRegistersPass` 81 | - {py:meth}`~pytket.passes.NormaliseTK2` [5] 82 | - {py:meth}`~pytket.passes.NormaliseTK2` [5] 83 | - {py:meth}`~pytket.passes.GreedyPauliSimp` 84 | * - 85 | - {py:meth}`~pytket.passes.DecomposeTK2` [5] 86 | - {py:meth}`~pytket.passes.DecomposeTK2` [5] 87 | - {py:meth}`~pytket.passes.NormaliseTK2` [5] 88 | * - 89 | - {py:meth}`~pytket.passes.AutoRebase` [2] 90 | - {py:meth}`~pytket.passes.AutoRebase` [2] 91 | - {py:meth}`~pytket.passes.DecomposeTK2` [5] 92 | * - 93 | - {py:meth}`~pytket.passes.ZZPhaseToRz` 94 | - {py:meth}`~pytket.passes.RemoveRedundancies` 95 | - {py:meth}`~pytket.passes.AutoRebase` [2] 96 | * - 97 | - {py:meth}`~pytket.passes.RemoveRedundancies` 98 | - {py:meth}`~pytket.passes.AutoSquash` [4] 99 | - {py:meth}`~pytket.passes.RemoveRedundancies` 100 | * - 101 | - {py:meth}`~pytket.passes.AutoSquash` [4] 102 | - {py:meth}`~pytket.passes.FlattenRelabelRegistersPass` 103 | - {py:meth}`~pytket.passes.AutoSquash` [4] 104 | * - 105 | - {py:meth}`~pytket.passes.FlattenRelabelRegistersPass` 106 | - {py:meth}`~pytket.passes.RemoveRedundancies` 107 | - {py:meth}`~pytket.passes.FlattenRelabelRegistersPass` 108 | * - 109 | - {py:meth}`~pytket.passes.RemoveRedundancies` 110 | - 111 | - {py:meth}`~pytket.passes.RemoveRedundancies` 112 | ::: 113 | 114 | - \[1\] If no value is specified then `optimisation_level` defaults to a value of 2. 115 | - \[2\] {py:meth}`~pytket.passes.AutoRebase` is a rebase that converts the circuit to the Quantinuum native gate set (e.g. $\{Rz, PhasedX, ZZMax, ZZPhase\}$). 116 | - \[3\] {py:meth}`~pytket.passes.FullPeepholeOptimise` has the argument `target_2qb_gate=OpType.TK2`. 117 | - \[4\] {py:meth}`~pytket.passes.AutoSquash` targets the $\{PhasedX, Rz\}$ gate set, i.e. `AutoSquash({OpType.PhasedX, OpType.Rz})`. 118 | - \[5\] Omitted if the target two-qubit gate is `OpType.TK2`. 119 | 120 | :::{note} 121 | If `optimisation_level = 0` the device constraints are solved but no additional optimisation is applied. Setting `optimisation_level = 1` applies some light optimisations to the circuit. More intensive optimisation is applied by level 2 at the expense of increased runtime. 122 | ::: 123 | 124 | :::{note} 125 | The pass {py:meth}`~pytket.passes.ZZPhaseToRz` is left out of `optimisation_level=2` as the passes applied by {py:meth}`~pytket.passes.FullPeepholeOptimise` will already cover these optimisations. 126 | ::: 127 | 128 | # Target Two-Qubit Gate 129 | 130 | Backends may offer several alternatives as the native two-qubit gate: the 131 | current possibilities are `ZZMax`, `ZZPhase` and `TK2`. The set of 132 | supported gates may be queried using the 133 | {py:attr}`~.QuantinuumBackend.two_qubit_gate_set` property. Each device also has a 134 | default two-qubit gate, which may be queried using the 135 | {py:attr}`~.QuantinuumBackend.default_two_qubit_gate` property. Currently, the default 136 | two-qubit gate for all devices is `ZZPhase`. 137 | 138 | The default compilation pass and rebase pass will target the default gate by 139 | default. This may be overridden using the method 140 | {py:meth}`~.QuantinuumBackend.set_compilation_config_target_2qb_gate` or by passing a 141 | {py:class}`~.QuantinuumBackendCompilationConfig` when constructing the backend. 142 | 143 | # Device Predicates 144 | 145 | Circuits must satisfy the following predicates in order to run on the {py:class}`~.QuantinuumBackend`. 146 | 147 | - {py:class}`~pytket.predicates.NoSymbolsPredicate`: Parameterised gates must have numerical parameters when the circuit is executed. 148 | - {py:class}`~pytket.predicates.GateSetPredicate`: To view supported Ops run `QuantinuumBackend.backend_info.gate_set`. 149 | - {py:class}`~pytket.predicates.MaxNQubitsPredicate`: `H2-1`, `H2-1E` and `H2-1SC` all support a maximum of 56 qubits. 150 | 151 | # Local Emulators 152 | 153 | If `pytket-quantinuum` is installed with the `pecos` option: 154 | 155 | ``` 156 | pip install pytket-quantinuum[pecos] 157 | ``` 158 | 159 | For `uv` virtual enviroments it is possible that this does not work, because prereleases are not picked up automatically. This can be solved by installing the latest `pytket-pecos` version via: 160 | 161 | ``` 162 | uv pip install pytket-pecos --prerelease=allow 163 | ``` 164 | 165 | then it is possible to run circuits on an emulator running on the local machine. 166 | 167 | Currently this emulation is noiseless. 168 | 169 | ```{eval-rst} 170 | .. toctree:: 171 | api.md 172 | changelog.md 173 | ``` 174 | 175 | ```{eval-rst} 176 | .. toctree:: 177 | :caption: Useful links 178 | 179 | Issue tracker 180 | PyPi 181 | ``` 182 | -------------------------------------------------------------------------------- /tests/unit/leakage_detection_test.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 | 16 | from collections import Counter 17 | from typing import TYPE_CHECKING, cast 18 | 19 | import pytest 20 | from pytket.backends.backendresult import BackendResult 21 | from pytket.utils.outcomearray import OutcomeArray 22 | 23 | from pytket import Bit, Circuit, OpType, Qubit # type: ignore 24 | from pytket.extensions.quantinuum.backends.leakage_gadget import ( 25 | LEAKAGE_DETECTION_BIT_NAME_, 26 | LEAKAGE_DETECTION_QUBIT_NAME_, 27 | get_detection_circuit, 28 | get_leakage_gadget_circuit, 29 | prune_shots_detected_as_leaky, 30 | ) 31 | 32 | if TYPE_CHECKING: 33 | from collections.abc import Sequence 34 | 35 | 36 | def test_postselection_circuits_1qb_task_gen() -> None: 37 | comparison_circuit: Circuit = Circuit(1, 1) 38 | lg_qb = Qubit(LEAKAGE_DETECTION_QUBIT_NAME_, 0) 39 | lg_b = Bit(LEAKAGE_DETECTION_BIT_NAME_, 0) 40 | comparison_circuit.add_qubit(lg_qb) 41 | comparison_circuit.add_bit(lg_b) 42 | comparison_circuit.add_gate(OpType.Reset, [lg_qb]) 43 | comparison_circuit.X(lg_qb).H(0) 44 | comparison_circuit.add_barrier([Qubit(0), lg_qb]) 45 | comparison_circuit.H(lg_qb).ZZMax(lg_qb, Qubit(0)) 46 | comparison_circuit.add_barrier([Qubit(0), lg_qb]) 47 | comparison_circuit.ZZMax(lg_qb, Qubit(0)).H(lg_qb).Z(0) 48 | comparison_circuit.add_barrier([Qubit(0), lg_qb]) 49 | comparison_circuit.Measure(0, 0).Measure(lg_qb, lg_b) 50 | assert comparison_circuit == get_detection_circuit( 51 | Circuit(1, 1).H(0).Measure(0, 0), 2 52 | ) 53 | 54 | 55 | def test_postselection_circuits_2qb_2_spare_task_gen() -> None: 56 | comparison_circuit = Circuit(2, 2) 57 | lg_qb0 = Qubit(LEAKAGE_DETECTION_QUBIT_NAME_, 0) 58 | lg_b0 = Bit(LEAKAGE_DETECTION_BIT_NAME_, 0) 59 | lg_qb1 = Qubit(LEAKAGE_DETECTION_QUBIT_NAME_, 1) 60 | lg_b1 = Bit(LEAKAGE_DETECTION_BIT_NAME_, 1) 61 | comparison_circuit.add_qubit(lg_qb0) 62 | comparison_circuit.add_bit(lg_b0) 63 | comparison_circuit.add_qubit(lg_qb1) 64 | comparison_circuit.add_bit(lg_b1) 65 | comparison_circuit.add_gate(OpType.Reset, [lg_qb0]) 66 | comparison_circuit.add_gate(OpType.Reset, [lg_qb1]) 67 | comparison_circuit.X(lg_qb0).X(lg_qb1).H(0).CZ(0, 1) 68 | comparison_circuit.add_barrier([Qubit(0), lg_qb0]) 69 | comparison_circuit.add_barrier([Qubit(1), lg_qb1]) 70 | comparison_circuit.H(lg_qb0).H(lg_qb1).ZZMax(lg_qb0, Qubit(0)).ZZMax( 71 | lg_qb1, Qubit(1) 72 | ) 73 | comparison_circuit.add_barrier([Qubit(0), lg_qb0]) 74 | comparison_circuit.add_barrier([Qubit(1), lg_qb1]) 75 | comparison_circuit.ZZMax(lg_qb0, Qubit(0)).ZZMax(lg_qb1, Qubit(1)) 76 | comparison_circuit.H(lg_qb0).H(lg_qb1).Z(0).Z(1) 77 | comparison_circuit.add_barrier([Qubit(0), lg_qb0]) 78 | comparison_circuit.add_barrier([Qubit(1), lg_qb1]) 79 | comparison_circuit.Measure(0, 0).Measure(1, 1).Measure(lg_qb0, lg_b0).Measure( 80 | lg_qb1, lg_b1 81 | ) 82 | 83 | assert comparison_circuit == get_detection_circuit( 84 | Circuit(2, 2).H(0).CZ(0, 1).Measure(0, 0).Measure(1, 1), 4 85 | ) 86 | 87 | 88 | def test_postselection_circuits_2qb_1_spare_task_gen() -> None: 89 | comparison_circuit = Circuit(2, 2) 90 | lg_qb = Qubit(LEAKAGE_DETECTION_QUBIT_NAME_, 0) 91 | lg_b0 = Bit(LEAKAGE_DETECTION_BIT_NAME_, 0) 92 | lg_b1 = Bit(LEAKAGE_DETECTION_BIT_NAME_, 1) 93 | comparison_circuit.add_qubit(lg_qb) 94 | comparison_circuit.add_bit(lg_b0) 95 | comparison_circuit.add_bit(lg_b1) 96 | comparison_circuit.add_gate(OpType.Reset, [lg_qb]) 97 | 98 | comparison_circuit.X(lg_qb).H(0).CZ(0, 1) 99 | comparison_circuit.add_barrier([Qubit(0), lg_qb]) 100 | comparison_circuit.H(lg_qb).ZZMax(lg_qb, Qubit(0)) 101 | comparison_circuit.add_barrier([Qubit(0), lg_qb]) 102 | comparison_circuit.ZZMax(lg_qb, Qubit(0)).H(lg_qb).Z(0) 103 | comparison_circuit.add_barrier([Qubit(0), lg_qb]) 104 | comparison_circuit.Measure(0, 0).Measure(lg_qb, lg_b0) 105 | 106 | # with reuse of pre-measured qubits, this will now use Qubit 0 from the circuit 107 | comparison_circuit.add_gate(OpType.Reset, [Qubit(0)]) 108 | comparison_circuit.X(Qubit(0)) 109 | comparison_circuit.add_barrier([Qubit(1), Qubit(0)]) 110 | comparison_circuit.H(Qubit(0)).ZZMax(Qubit(0), Qubit(1)) 111 | comparison_circuit.add_barrier([Qubit(1), Qubit(0)]) 112 | comparison_circuit.ZZMax(Qubit(0), Qubit(1)).H(Qubit(0)).Z(1) 113 | comparison_circuit.add_barrier([Qubit(1), Qubit(0)]) 114 | comparison_circuit.Measure(1, 1).Measure(Qubit(0), lg_b1) 115 | 116 | assert comparison_circuit == get_detection_circuit( 117 | Circuit(2, 2).H(0).CZ(0, 1).Measure(0, 0).Measure(1, 1), 3 118 | ) 119 | 120 | 121 | def test_postselection_no_qubits() -> None: 122 | with pytest.raises(ValueError): 123 | get_detection_circuit(Circuit(0), 1) 124 | 125 | 126 | def test_postselection_not_enough_device_qubits_0() -> None: 127 | comparison_circuit = Circuit(2, 2) 128 | comparison_circuit.CX(0, 1).Measure(0, 0) 129 | comparison_circuit.append( 130 | get_leakage_gadget_circuit( 131 | Qubit(1), Qubit(0), Bit(LEAKAGE_DETECTION_BIT_NAME_, 0) 132 | ) 133 | ) 134 | comparison_circuit.Measure(1, 1) 135 | assert comparison_circuit == get_detection_circuit( 136 | Circuit(2, 2).CX(0, 1).measure_all(), 2 137 | ) 138 | 139 | 140 | def test_postselection_not_enough_device_qubits_1() -> None: 141 | comparison_circuit = Circuit(4, 4) 142 | comparison_circuit.CX(0, 1).Measure(0, 0).CX(1, 2) 143 | comparison_circuit.CX(2, 3) 144 | comparison_circuit.append( 145 | get_leakage_gadget_circuit( 146 | Qubit(1), Qubit(0), Bit(LEAKAGE_DETECTION_BIT_NAME_, 0) 147 | ) 148 | ) 149 | comparison_circuit.Measure(1, 1) 150 | comparison_circuit.append( 151 | get_leakage_gadget_circuit( 152 | Qubit(2), Qubit(1), Bit(LEAKAGE_DETECTION_BIT_NAME_, 1) 153 | ) 154 | ) 155 | comparison_circuit.Measure(2, 2) 156 | comparison_circuit.append( 157 | get_leakage_gadget_circuit( 158 | Qubit(3), Qubit(2), Bit(LEAKAGE_DETECTION_BIT_NAME_, 2) 159 | ) 160 | ) 161 | comparison_circuit.Measure(3, 3) 162 | assert comparison_circuit == get_detection_circuit( 163 | Circuit(4, 4).CX(0, 1).CX(1, 2).CX(2, 3).measure_all(), 4 164 | ) 165 | 166 | 167 | def test_postselection_existing_qubit() -> None: 168 | lg_qb = Qubit(LEAKAGE_DETECTION_QUBIT_NAME_, 0) 169 | c = Circuit(1, 2).X(0) 170 | c.add_qubit(lg_qb) 171 | c.X(lg_qb) 172 | c.Measure(0, 0) 173 | c.Measure(lg_qb, Bit(1)) 174 | with pytest.raises(ValueError): 175 | get_detection_circuit(c, 2) 176 | 177 | 178 | def test_postselection_existing_bit() -> None: 179 | lg_b = Bit(LEAKAGE_DETECTION_BIT_NAME_, 0) 180 | c = Circuit(2, 1).CX(0, 1) 181 | c.add_bit(lg_b) 182 | c.Measure(0, 0) 183 | c.Measure(Qubit(1), lg_b) 184 | 185 | with pytest.raises(ValueError): 186 | get_detection_circuit(c, 2) 187 | 188 | 189 | def test_postselection_discard_0() -> None: 190 | counter_dict = {(0, 0): 100, (0, 1): 75, (1, 0): 50, (1, 1): 25} 191 | counts = Counter( 192 | {OutcomeArray.from_readouts([key]): val for key, val in counter_dict.items()} 193 | ) 194 | 195 | discard_result = prune_shots_detected_as_leaky( 196 | BackendResult( 197 | counts=counts, 198 | c_bits=cast("Sequence[Bit]", [Bit(0), Bit(LEAKAGE_DETECTION_BIT_NAME_, 0)]), 199 | ) 200 | ).get_counts() 201 | assert discard_result[(0,)] == 100 202 | assert discard_result[(1,)] == 50 203 | 204 | 205 | def test_postselection_discard_1() -> None: 206 | raw_readouts = ( 207 | [[0, 0, 0, 0]] * 100 208 | + [[0, 1, 0, 0]] * 75 209 | + [[0, 1, 0, 1]] * 50 210 | + [[1, 0, 1, 0]] * 33 211 | ) 212 | outcomes = OutcomeArray.from_readouts(raw_readouts) 213 | 214 | backres_shots = BackendResult( 215 | shots=outcomes, 216 | c_bits=[ 217 | Bit(3), 218 | Bit(LEAKAGE_DETECTION_BIT_NAME_, 23), 219 | Bit("a", 7), 220 | Bit(LEAKAGE_DETECTION_BIT_NAME_, 3), 221 | ], 222 | ) 223 | discard_result = prune_shots_detected_as_leaky(backres_shots).get_counts() 224 | assert discard_result[(0, 0)] == 100 225 | assert discard_result[(1, 1)] == 33 226 | 227 | 228 | if __name__ == "__main__": 229 | test_postselection_circuits_1qb_task_gen() 230 | test_postselection_circuits_2qb_2_spare_task_gen() 231 | test_postselection_circuits_2qb_1_spare_task_gen() 232 | test_postselection_discard_0() 233 | test_postselection_discard_1() 234 | test_postselection_existing_qubit() 235 | test_postselection_existing_bit() 236 | test_postselection_no_qubits() 237 | test_postselection_not_enough_device_qubits_0() 238 | test_postselection_not_enough_device_qubits_1() 239 | -------------------------------------------------------------------------------- /tests/unit/convert_test.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 | from pytket.architecture import FullyConnected 17 | from pytket.backends.backendinfo import BackendInfo 18 | from pytket.circuit import Circuit, OpType, Qubit 19 | from pytket.qasm import circuit_to_qasm_str 20 | 21 | from pytket.extensions.quantinuum import QuantinuumBackend 22 | from pytket.extensions.quantinuum.backends.api_wrappers import QuantinuumAPIError 23 | 24 | 25 | def test_convert() -> None: 26 | circ = Circuit(4) 27 | circ.H(0).CX(0, 1) 28 | circ.add_gate(OpType.noop, [1]) 29 | circ.CRz(0.5, 1, 2) 30 | circ.add_barrier([2]) 31 | circ.measure_all() 32 | 33 | b = QuantinuumBackend("", machine_debug=True) 34 | b.set_compilation_config_target_2qb_gate(OpType.ZZMax) 35 | b.set_compilation_config_allow_implicit_swaps(False) 36 | b.rebase_pass().apply(circ) 37 | circ_quum = circuit_to_qasm_str(circ, header="hqslib1") 38 | qasm_str = circ_quum.split("\n")[6:-1] 39 | assert all( 40 | any(com.startswith(gate) for gate in ("rz", "U1q", "ZZ", "measure", "barrier")) 41 | for com in qasm_str 42 | ) 43 | 44 | 45 | def test_convert_rzz() -> None: 46 | circ = Circuit(4) 47 | circ.Rz(0.5, 1) 48 | circ.add_gate(OpType.PhasedX, [0.2, 0.3], [1]) 49 | circ.ZZPhase(0.3, 2, 3) 50 | circ.add_gate(OpType.ZZMax, [2, 3]) 51 | circ.measure_all() 52 | 53 | b = QuantinuumBackend("", machine_debug=True) 54 | b.set_compilation_config_allow_implicit_swaps(False) 55 | b.rebase_pass().apply(circ) 56 | circ_quum = circuit_to_qasm_str(circ, header="hqslib1") 57 | qasm_str = circ_quum.split("\n")[6:-1] 58 | assert all( 59 | any(com.startswith(gate) for gate in ("rz", "U1q", "ZZ", "measure", "RZZ")) 60 | for com in qasm_str 61 | ) 62 | 63 | 64 | def test_implicit_swap_removal() -> None: # noqa: PLR0915 65 | b = QuantinuumBackend("", machine_debug=True) 66 | c = Circuit(2).ISWAPMax(0, 1) 67 | b.set_compilation_config_target_2qb_gate(OpType.ZZMax) 68 | compiled = b.get_compiled_circuit(c, 0) 69 | assert compiled.n_gates_of_type(OpType.ZZMax) == 1 70 | assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 71 | iqp = compiled.implicit_qubit_permutation() 72 | assert iqp[Qubit(0)] == Qubit(1) 73 | assert iqp[Qubit(1)] == Qubit(0) 74 | b.set_compilation_config_allow_implicit_swaps(False) 75 | c = b.get_compiled_circuit(Circuit(2).ISWAPMax(0, 1), 0) 76 | assert c.n_gates_of_type(OpType.ZZMax) == 2 77 | assert c.n_gates_of_type(OpType.ZZPhase) == 0 78 | 79 | c = Circuit(2).Sycamore(0, 1) 80 | b.set_compilation_config_allow_implicit_swaps(True) 81 | compiled = b.get_compiled_circuit(c, 0) 82 | assert compiled.n_gates_of_type(OpType.ZZMax) == 2 83 | assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 84 | iqp = compiled.implicit_qubit_permutation() 85 | assert iqp[Qubit(0)] == Qubit(1) 86 | assert iqp[Qubit(1)] == Qubit(0) 87 | b.set_compilation_config_allow_implicit_swaps(False) 88 | c = b.get_compiled_circuit(Circuit(2).Sycamore(0, 1), 0) 89 | assert c.n_gates_of_type(OpType.ZZMax) == 3 90 | assert c.n_gates_of_type(OpType.ZZPhase) == 0 91 | 92 | c = Circuit(2).ISWAP(0.3, 0, 1) 93 | compiled = b.get_compiled_circuit(c, 0) 94 | assert compiled.n_gates_of_type(OpType.ZZMax) == 2 95 | assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 96 | iqp = compiled.implicit_qubit_permutation() 97 | assert iqp[Qubit(0)] == Qubit(0) 98 | assert iqp[Qubit(1)] == Qubit(1) 99 | c = b.get_compiled_circuit(Circuit(2).ISWAP(0.3, 0, 1), 0) 100 | assert c.n_gates_of_type(OpType.ZZMax) == 2 101 | assert c.n_gates_of_type(OpType.ZZPhase) == 0 102 | 103 | c = Circuit(2).ISWAPMax(0, 1).ISWAPMax(1, 0) 104 | b.set_compilation_config_allow_implicit_swaps(True) 105 | compiled = b.get_compiled_circuit(c, 0) 106 | assert compiled.n_gates_of_type(OpType.ZZMax) == 2 107 | assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 108 | iqp = compiled.implicit_qubit_permutation() 109 | assert iqp[Qubit(0)] == Qubit(0) 110 | assert iqp[Qubit(1)] == Qubit(1) 111 | c = Circuit(2).ISWAPMax(0, 1).ISWAPMax(1, 0) 112 | compiled = b.get_compiled_circuit(c, 1) 113 | assert compiled.n_gates_of_type(OpType.ZZMax) == 2 114 | assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 115 | iqp = compiled.implicit_qubit_permutation() 116 | assert iqp[Qubit(0)] == Qubit(0) 117 | b.set_compilation_config_allow_implicit_swaps(False) 118 | c = b.get_compiled_circuit(Circuit(2).ISWAPMax(0, 1).ISWAPMax(1, 0), 0) 119 | assert c.n_gates_of_type(OpType.ZZMax) == 4 120 | assert c.n_gates_of_type(OpType.ZZPhase) == 0 121 | 122 | c = Circuit(2).SWAP(0, 1) 123 | b.set_compilation_config_allow_implicit_swaps(True) 124 | b.set_compilation_config_target_2qb_gate(OpType.ZZPhase) 125 | compiled = b.get_compiled_circuit(c, 0) 126 | assert compiled.n_gates_of_type(OpType.ZZMax) == 0 127 | assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 128 | iqp = compiled.implicit_qubit_permutation() 129 | assert iqp[Qubit(0)] == Qubit(1) 130 | assert iqp[Qubit(1)] == Qubit(0) 131 | b.set_compilation_config_allow_implicit_swaps(False) 132 | b.set_compilation_config_target_2qb_gate(OpType.ZZMax) 133 | c = b.get_compiled_circuit(Circuit(2).SWAP(0, 1), 0) 134 | assert c.n_gates_of_type(OpType.ZZMax) == 3 135 | assert c.n_gates_of_type(OpType.ZZPhase) == 0 136 | 137 | c = Circuit(2).ZZMax(0, 1) 138 | compiled = b.get_compiled_circuit(c, 0) 139 | assert compiled.n_gates == 1 140 | 141 | 142 | def test_switch_target_2qb_gate() -> None: 143 | # this default device only "supports" ZZMax 144 | b = QuantinuumBackend("", machine_debug=True) 145 | c = Circuit(2).ISWAPMax(0, 1) 146 | # Default behaviour 147 | compiled = b.get_compiled_circuit(c, 0) 148 | assert compiled.n_gates_of_type(OpType.ZZMax) == 0 149 | assert compiled.n_gates_of_type(OpType.ZZPhase) == 1 150 | assert compiled.n_gates_of_type(OpType.TK2) == 0 151 | # Targeting allowed gate 152 | b.set_compilation_config_target_2qb_gate(OpType.ZZMax) 153 | compiled = b.get_compiled_circuit(c, 0) 154 | assert compiled.n_gates_of_type(OpType.ZZMax) == 1 155 | assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 156 | assert compiled.n_gates_of_type(OpType.TK2) == 0 157 | # Targeting allowed gate but no wire swap 158 | b.set_compilation_config_allow_implicit_swaps(False) 159 | compiled = b.get_compiled_circuit(c, 0) 160 | assert compiled.n_gates_of_type(OpType.ZZMax) == 2 161 | assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 162 | assert compiled.n_gates_of_type(OpType.TK2) == 0 163 | # Targeting unsupported gate 164 | with pytest.raises(QuantinuumAPIError): 165 | b.set_compilation_config_target_2qb_gate(OpType.ISWAPMax) 166 | 167 | # Confirming that if ZZPhase is added to gate set that it functions 168 | b._MACHINE_DEBUG = False # noqa: SLF001 169 | b._backend_info = BackendInfo( # noqa: SLF001 170 | name="test", 171 | device_name="test", 172 | version="test", 173 | architecture=FullyConnected(1), 174 | gate_set={OpType.ZZPhase, OpType.ZZMax, OpType.PhasedX, OpType.Rz}, 175 | ) 176 | assert OpType.ZZMax in b._gate_set # noqa: SLF001 177 | assert OpType.ZZPhase in b._gate_set # noqa: SLF001 178 | b.set_compilation_config_allow_implicit_swaps(True) 179 | b.set_compilation_config_target_2qb_gate(OpType.ZZPhase) 180 | compiled = b.get_compiled_circuit(c, 0) 181 | assert compiled.n_gates_of_type(OpType.ZZMax) == 0 182 | assert compiled.n_gates_of_type(OpType.ZZPhase) == 1 183 | assert compiled.n_gates_of_type(OpType.TK2) == 0 184 | 185 | 186 | def test_not_allow_implicit_swaps() -> None: 187 | # https://github.com/CQCL/pytket-quantinuum/issues/515 188 | b = QuantinuumBackend("", machine_debug=True) 189 | circuits = [Circuit(2).SWAP(0, 1), Circuit(2).CX(0, 1).CX(1, 0).CX(0, 1)] 190 | b.set_compilation_config_allow_implicit_swaps(False) 191 | for target_gate in [OpType.ZZMax, OpType.ZZPhase, OpType.TK2]: 192 | for c in circuits: 193 | b.set_compilation_config_target_2qb_gate(target_gate) 194 | d0 = b.get_compiled_circuit(c, optimisation_level=0) 195 | d1 = b.get_compiled_circuit(c, optimisation_level=1) 196 | d2 = b.get_compiled_circuit(c, optimisation_level=2) 197 | assert d0.implicit_qubit_permutation() == { 198 | Qubit(0): Qubit(0), 199 | Qubit(1): Qubit(1), 200 | } 201 | assert d1.implicit_qubit_permutation() == { 202 | Qubit(0): Qubit(0), 203 | Qubit(1): Qubit(1), 204 | } 205 | assert d2.implicit_qubit_permutation() == { 206 | Qubit(0): Qubit(0), 207 | Qubit(1): Qubit(1), 208 | } 209 | 210 | 211 | if __name__ == "__main__": 212 | test_implicit_swap_removal() 213 | test_switch_target_2qb_gate() 214 | test_not_allow_implicit_swaps() 215 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /tests/integration/local_emulator_test.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 collections import Counter 16 | from pathlib import Path 17 | 18 | import numpy as np 19 | import pytest 20 | from pytket.circuit import ( 21 | Bit, 22 | Circuit, 23 | Qubit, 24 | if_not_bit, 25 | reg_eq, 26 | reg_geq, 27 | reg_gt, 28 | reg_leq, 29 | reg_lt, 30 | reg_neq, 31 | ) 32 | from pytket.circuit.clexpr import wired_clexpr_from_logic_exp 33 | from pytket.wasm import WasmFileHandler 34 | 35 | from pytket.extensions.quantinuum import QuantinuumBackend, have_pecos 36 | 37 | 38 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 39 | def test_local_emulator() -> None: 40 | b = QuantinuumBackend("H2-1LE") 41 | assert b.is_local_emulator 42 | c0 = Circuit(2).X(0).CX(0, 1).measure_all() 43 | c = b.get_compiled_circuit(c0) 44 | h = b.process_circuit(c, n_shots=10) 45 | r = b.get_result(h) 46 | counts = r.get_counts() 47 | assert counts == Counter({(1, 1): 10}) 48 | 49 | 50 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 51 | def test_circuit_with_conditional() -> None: 52 | b = QuantinuumBackend("H2-1LE") 53 | c0 = Circuit(2, 2).H(0) 54 | c0.Measure(0, 0) 55 | c0.X(1, condition_bits=[0], condition_value=1) 56 | c0.Measure(1, 1) 57 | c = b.get_compiled_circuit(c0) 58 | h = b.process_circuit(c, n_shots=10) 59 | r = b.get_result(h) 60 | counts = r.get_counts() 61 | assert sum(counts.values()) == 10 62 | assert all(v0 == v1 for v0, v1 in counts) 63 | 64 | 65 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 66 | def test_results_order() -> None: 67 | b = QuantinuumBackend("H2-1LE") 68 | c0 = Circuit(2).X(0).measure_all() 69 | c = b.get_compiled_circuit(c0) 70 | h = b.process_circuit(c, n_shots=10) 71 | r = b.get_result(h) 72 | counts = r.get_counts() 73 | assert counts == Counter({(1, 0): 10}) 74 | 75 | 76 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 77 | def test_multireg() -> None: 78 | b = QuantinuumBackend("H2-1LE") 79 | c = Circuit() 80 | q1 = Qubit("q1", 0) 81 | q2 = Qubit("q2", 0) 82 | c1 = Bit("c1", 0) 83 | c2 = Bit("c2", 0) 84 | for q in (q1, q2): 85 | c.add_qubit(q) 86 | for cb in (c1, c2): 87 | c.add_bit(cb) 88 | c.H(q1) 89 | c.CX(q1, q2) 90 | c.Measure(q1, c1) 91 | c.Measure(q2, c2) 92 | c = b.get_compiled_circuit(c) 93 | 94 | n_shots = 10 95 | counts = b.run_circuit(c, n_shots=n_shots).get_counts() 96 | assert sum(counts.values()) == 10 97 | assert all(v0 == v1 for v0, v1 in counts) 98 | 99 | 100 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 101 | def test_setbits() -> None: 102 | b = QuantinuumBackend("H2-1LE") 103 | c = Circuit(1, 3) 104 | c.H(0) 105 | c.Measure(0, 0) 106 | c.add_c_setbits([True, False, True], c.bits) 107 | c = b.get_compiled_circuit(c) 108 | n_shots = 10 109 | counts = b.run_circuit(c, n_shots=n_shots).get_counts() 110 | assert counts == Counter({(1, 0, 1): n_shots}) 111 | 112 | 113 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 114 | def test_classical_0() -> None: 115 | c = Circuit(1) 116 | a = c.add_c_register("a", 8) 117 | b = c.add_c_register("b", 10) 118 | d = c.add_c_register("d", 10) 119 | 120 | c.add_c_setbits([True], [a[0]]) 121 | c.add_c_setbits([False, True] + [False] * 6, a) # type: ignore 122 | c.add_c_setbits([True, True] + [False] * 8, b) # type: ignore 123 | 124 | c.add_c_setreg(23, a) 125 | c.add_c_copyreg(a, b) 126 | 127 | c.add_clexpr(*wired_clexpr_from_logic_exp(a + b, d.to_list())) 128 | c.add_clexpr(*wired_clexpr_from_logic_exp(a - b, d.to_list())) 129 | c.add_clexpr(*wired_clexpr_from_logic_exp(a * b * d, d.to_list())) 130 | c.add_clexpr(*wired_clexpr_from_logic_exp(a << 1, a.to_list())) 131 | c.add_clexpr(*wired_clexpr_from_logic_exp(a >> 1, b.to_list())) 132 | 133 | c.X(0, condition=reg_eq(a ^ b, 1)) 134 | c.X(0, condition=reg_eq(a & b, 1)) 135 | c.X(0, condition=reg_eq(a | b, 1)) 136 | 137 | c.X(0, condition=a[0]) 138 | c.X(0, condition=reg_neq(a, 1)) 139 | c.X(0, condition=if_not_bit(a[0])) 140 | c.X(0, condition=reg_gt(a, 1)) 141 | c.X(0, condition=reg_lt(a, 1)) 142 | c.X(0, condition=reg_geq(a, 1)) 143 | c.X(0, condition=reg_leq(a, 1)) 144 | c.Phase(0, condition=a[0]) 145 | c.Measure(Qubit(0), d[0]) 146 | 147 | backend = QuantinuumBackend("H2-1LE") 148 | 149 | c = backend.get_compiled_circuit(c) 150 | counts = backend.run_circuit(c, n_shots=10).get_counts() 151 | assert len(counts.values()) == 1 152 | 153 | 154 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 155 | def test_classical_1() -> None: 156 | c = Circuit(1) 157 | a = c.add_c_register("a", 8) 158 | b = c.add_c_register("b", 10) 159 | d = c.add_c_register("d", 10) 160 | 161 | c.add_c_setbits([True], [a[0]]) 162 | c.add_c_setbits([False, True] + [False] * 6, a) # type: ignore 163 | c.add_c_setbits([True, True] + [False] * 8, b) # type: ignore 164 | 165 | c.add_c_setreg(23, a) 166 | c.add_c_copyreg(a, b) 167 | 168 | c.add_clexpr(*wired_clexpr_from_logic_exp(a + b, d.to_list())) 169 | c.add_clexpr(*wired_clexpr_from_logic_exp(a - b, d.to_list())) 170 | c.add_clexpr(*wired_clexpr_from_logic_exp(a * b * d, d.to_list())) 171 | c.add_clexpr(*wired_clexpr_from_logic_exp(a << 1, a.to_list())) 172 | c.add_clexpr(*wired_clexpr_from_logic_exp(a >> 1, b.to_list())) 173 | 174 | c.X(0, condition=reg_eq(a ^ b, 1)) 175 | c.Measure(Qubit(0), d[0]) 176 | 177 | backend = QuantinuumBackend("H2-1LE") 178 | 179 | c = backend.get_compiled_circuit(c) 180 | counts = backend.run_circuit(c, n_shots=10).get_counts() 181 | assert len(counts.values()) == 1 182 | 183 | 184 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 185 | def test_classical_2() -> None: 186 | circ = Circuit(1) 187 | a = circ.add_c_register("a", 2) 188 | b = circ.add_c_register("b", 2) 189 | c = circ.add_c_register("c", 1) 190 | expr = a[0] ^ b[0] 191 | circ.add_clexpr(*wired_clexpr_from_logic_exp(expr, [c[0]])) 192 | circ.X(0) 193 | circ.Measure(Qubit(0), a[1]) 194 | backend = QuantinuumBackend("H2-1LE") 195 | cc = backend.get_compiled_circuit(circ) 196 | counts = backend.run_circuit(cc, n_shots=10).get_counts() 197 | assert len(counts.keys()) == 1 198 | 199 | 200 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 201 | def test_classical_3() -> None: 202 | circ = Circuit(1) 203 | a = circ.add_c_register("a", 4) 204 | b = circ.add_c_register("b", 4) 205 | c = circ.add_c_register("c", 4) 206 | 207 | circ.add_c_setreg(3, a) 208 | circ.add_c_copyreg(a, b) 209 | 210 | circ.add_clexpr(*wired_clexpr_from_logic_exp(a - b, c.to_list())) 211 | circ.add_clexpr(*wired_clexpr_from_logic_exp(a << 1, a.to_list())) 212 | 213 | circ.X(0) 214 | circ.Measure(Qubit(0), a[3]) 215 | 216 | backend = QuantinuumBackend("H2-1LE") 217 | 218 | cc = backend.get_compiled_circuit(circ) 219 | counts = backend.run_circuit(cc, n_shots=10).get_counts() 220 | assert len(counts.keys()) == 1 221 | result = list(counts.keys())[0] # noqa: RUF015 222 | assert result == (0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0) 223 | 224 | 225 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 226 | def test_classical_4() -> None: 227 | # https://github.com/CQCL/pytket-quantinuum/issues/395 228 | circ = Circuit(1) 229 | ctrl = circ.add_c_register(name="control", size=1) 230 | meas = circ.add_c_register(name="measure", size=1) 231 | circ.add_c_setreg(1, ctrl) 232 | circ.X(0, condition=ctrl[0]) 233 | circ.add_c_setreg(0, ctrl) 234 | circ.X(0, condition=ctrl[0]) 235 | circ.add_c_setreg(1, ctrl) 236 | circ.X(0, condition=ctrl[0]) 237 | circ.Measure( 238 | qubit=circ.qubits[0], 239 | bit=meas[0], 240 | ) 241 | backend = QuantinuumBackend("H2-1LE") 242 | compiled_circ = backend.get_compiled_circuit(circ, optimisation_level=0) 243 | result = backend.run_circuit(compiled_circ, n_shots=100, no_opt=True) 244 | counts = result.get_counts(meas.to_list()) 245 | assert counts == Counter({(0,): 100}) 246 | 247 | 248 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 249 | def test_classical_5() -> None: 250 | # https://github.com/CQCL/pytket-phir/issues/159 251 | circ = Circuit() 252 | targ_reg = circ.add_c_register("targ_reg", 1) 253 | ctrl_reg = circ.add_c_register("ctrl_reg", 1) 254 | circ.add_c_not(arg_in=targ_reg[0], arg_out=targ_reg[0], condition=ctrl_reg[0]) 255 | backend = QuantinuumBackend("H2-1LE") 256 | compiled_circ = backend.get_compiled_circuit(circuit=circ) 257 | result = backend.run_circuit(circuit=compiled_circ, n_shots=10) 258 | counts = result.get_counts() 259 | assert counts == Counter({(0, 0): 10}) 260 | 261 | 262 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 263 | def test_wasm() -> None: 264 | wasfile = WasmFileHandler(str(Path(__file__).parent.parent / "wasm" / "add1.wasm")) 265 | c = Circuit(1) 266 | a = c.add_c_register("a", 8) 267 | c.add_wasm_to_reg("add_one", wasfile, [a], [a]) 268 | 269 | b = QuantinuumBackend("H2-1LE") 270 | 271 | c = b.get_compiled_circuit(c) 272 | n_shots = 10 273 | counts = b.run_circuit(c, wasm_file_handler=wasfile, n_shots=n_shots).get_counts() 274 | assert counts == Counter({(1, 0, 0, 0, 0, 0, 0, 0): n_shots}) 275 | 276 | 277 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 278 | # Same as test_wasm_collatz() in backend_test.py but run on local emulators. 279 | def test_wasm_collatz() -> None: 280 | wasfile = WasmFileHandler( 281 | str(Path(__file__).parent.parent / "wasm" / "collatz.wasm") 282 | ) 283 | c = Circuit(8) 284 | a = c.add_c_register("a", 8) 285 | b = c.add_c_register("b", 8) 286 | 287 | # Use Hadamards to set "a" register to a random value. 288 | for i in range(8): 289 | c.H(i) 290 | c.Measure(Qubit(i), Bit("a", i)) 291 | # Compute the value of the Collatz function on this value. 292 | c.add_wasm_to_reg("collatz", wasfile, [a], [b]) 293 | 294 | backend = QuantinuumBackend("H2-1LE") 295 | 296 | c = backend.get_compiled_circuit(c) 297 | h = backend.process_circuit(c, n_shots=10, wasm_file_handler=wasfile) 298 | 299 | r = backend.get_result(h) 300 | shots = r.get_shots() 301 | 302 | def to_int(C: np.ndarray) -> int: 303 | assert len(C) == 8 304 | return int(sum(pow(2, i) * C[i] for i in range(8))) 305 | 306 | def collatz(n: int) -> int: 307 | if n == 0: 308 | return 0 309 | m = 0 310 | while n != 1: 311 | n = (3 * n + 1) // 2 if n % 2 == 1 else n // 2 312 | m += 1 313 | return m 314 | 315 | for shot in shots: 316 | n, m = to_int(shot[:8]), to_int(shot[8:16]) 317 | assert collatz(n) == m 318 | 319 | 320 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 321 | def test_midcircuit_measurement_and_reset() -> None: 322 | c = Circuit(1, 4) 323 | c.X(0) 324 | c.Measure(0, 0) 325 | c.Reset(0) 326 | c.Measure(0, 1) 327 | c.X(0) 328 | c.Measure(0, 2) 329 | c.Measure(0, 3) 330 | 331 | b = QuantinuumBackend("H2-1LE") 332 | 333 | c = b.get_compiled_circuit(c) 334 | n_shots = 10 335 | counts = b.run_circuit(c, n_shots=n_shots).get_counts() 336 | assert counts == Counter({(1, 0, 1, 1): n_shots}) 337 | 338 | 339 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 340 | def test_cbits() -> None: 341 | circ = Circuit(1) 342 | a = circ.add_c_register("a", 2) 343 | b = circ.add_c_register("b", 2) 344 | 345 | circ.add_c_setreg(3, a) 346 | circ.add_c_copyreg(a, b) 347 | circ.X(0) 348 | circ.Measure(Qubit(0), a[0]) 349 | 350 | backend = QuantinuumBackend("H2-1LE") 351 | 352 | cc = backend.get_compiled_circuit(circ) 353 | r = backend.run_circuit(cc, n_shots=1) 354 | counts = r.get_counts(cbits=list(a)) 355 | assert counts == Counter({(1, 1): 1}) 356 | 357 | 358 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 359 | def test_result_handling_for_empty_bits() -> None: 360 | # https://github.com/CQCL/pytket-quantinuum/issues/473 361 | 362 | circuit = Circuit(1, 2) 363 | circuit.X(0, condition=circuit.bits[1]) 364 | 365 | backend = QuantinuumBackend("H2-1LE") 366 | 367 | compiled_circuit = backend.get_compiled_circuit(circuit=circuit) 368 | result = backend.run_circuit( 369 | circuit=compiled_circuit, 370 | n_shots=1, 371 | ) 372 | assert result.get_counts() == {(0, 0): 1} 373 | 374 | 375 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 376 | def test_no_noise() -> None: 377 | # https://github.com/CQCL/pytket-quantinuum/issues/571 378 | 379 | backend = QuantinuumBackend("H2-1LE") 380 | backend_info = backend.backend_info 381 | assert backend_info is not None 382 | assert "noise_specs" not in backend_info.misc 383 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | ```{eval-rst} 2 | .. currentmodule:: pytket.extensions.quantinuum 3 | ``` 4 | 5 | # Changelog 6 | 7 | ## 0.56.2 (December 2025) 8 | 9 | - fix to quantum-pecos to 0.7.0.dev4 10 | 11 | ## 0.56.1 (December 2025) 12 | 13 | - Remove all methods providing remote access to Quantinuum devices. (Please use 14 | `qnexus` instead.) 15 | 16 | ## 0.55.2 (December 2025) 17 | 18 | - fix to quantum-pecos to 0.7.0.dev4 19 | 20 | ## 0.55.1 (November 2025) 21 | 22 | - Add static method `pass_from_info` to `QuantinuumBackend` so that we can compile directly from a `BackendInfo`. 23 | - Remove references to H1 except in offline API; update offline machine data. 24 | - Update pytket-pecos version requirement to 0.2.2. 25 | 26 | ## 0.54.0 (October 2025) 27 | 28 | - Fix request_options for batch submissions 29 | - Update pytket version requirement to 2.9.3. 30 | - Update pytket-qir version requirement to 0.25. 31 | 32 | ## 0.53.0 (August 2025) 33 | 34 | - Update pytket version requirement to 2.9.1. 35 | - Update pytket-pecos version requirement to 0.2.1. 36 | - Allow 64-bit classical registers. 37 | 38 | ## 0.52.0 (July 2025) 39 | 40 | - Add optional argument `request_raw_results` to `QuantinuumAPI.retrieve_job_status`. 41 | 42 | ## 0.51.0 (July 2025) 43 | 44 | - Update list of accepted op types for validity check. 45 | - Update pytket minimum version requirement to 2.7.0. 46 | 47 | ## 0.50.0 (June 2025) 48 | 49 | - Update pytket-qir minimum version requirement to 0.24.1. 50 | - Emit deprecation warning from submit_program(). 51 | - Add optional `data` argument to `QuantinuumBackend` constructor to allow 52 | offline usage (as compilation engine or local emulator) when device data are 53 | known. 54 | - Update pytket minimum version requirement to 2.6.0. 55 | 56 | ## 0.48.0 (May 2025) 57 | 58 | - Update pytket minimum version requirement to 2.4.1. 59 | - Update pytket-qir minimum version requirement to 0.23. 60 | - Add `start-date` to fields returned as part of circuit status. 61 | 62 | ## 0.47.0 (May 2025) 63 | 64 | - Add `max_cost` parameter to {py:meth}`~.QuantinuumBackend.process_circuits` and `submit_program()`. 65 | 66 | ## 0.46.0 (April 2025) 67 | 68 | - Update pytket minimum version requirement to 2.2.0. 69 | - Update pytket-qir minimum version requirement to 0.22. 70 | 71 | ## 0.45.0 (March 2025) 72 | 73 | - Add {py:class}`~pytket.predicates.CliffordCircuitPredicate` for circuit compilation and processing 74 | when using the stabilizer simulator. 75 | - Update pytket version requirement to 2.0.1. 76 | - Update pytket-qir version requirement to 0.21. 77 | - Update pytket-pecos version requirement to 0.2.0. 78 | 79 | ## 0.44.0 (February 2025) 80 | 81 | - Remove `noise_specs` from local-emulator backends. 82 | - Updated pytket version requirement to 1.39. 83 | - Updated pytket-qir version requirement to 0.20. 84 | 85 | ## 0.43.0 (January 2025) 86 | 87 | - Check for language support when submitting programs. 88 | - Remove all `Phase` operations from circuit when compiling for backend. 89 | - Updated pytket version requirement to 1.39. 90 | 91 | ## 0.42.0 (December 2024) 92 | 93 | - Updated pytket version requirement to 1.37. 94 | - Updated pytket-pecos version requirement to 0.1.32. 95 | 96 | ## 0.41.0 (November 2024) 97 | 98 | - Add optimisation level 3 that uses {py:meth}`~pytket.passes.GreedyPauliSimp`. 99 | 100 | ## 0.40.0 (November 2024) 101 | 102 | - Update pytket-qir version requirement to 0.17. 103 | - Updated pytket version requirement to 1.34. 104 | - Updated pytket-pecos version requirement to 0.1.31. 105 | - Allow circuits containing `OpType.ClExpr` operations. 106 | 107 | ## 0.39.0 (November 2024) 108 | 109 | - Use QIR by default for program submission. 110 | - Update pytket-qir version requirement to 0.16. 111 | - Add new `kwarg` `n_leakage_detection_qubits` to {py:meth}`~.QuantinuumBackend.process_circuits` 112 | - Fix `allow_implicit_swaps` configuration not handled correctly in default passes. 113 | - Add handling for negative results 114 | 115 | ## 0.38.1 (October 2024) 116 | 117 | - Query device information only when needed to avoid unnecessary API calls. 118 | - Update minimum versions of some dependencies (requests, websockets). 119 | 120 | ## 0.38.0 (October 2024) 121 | 122 | - Updated pytket version requirement to 1.33. 123 | - Fix handling of unused bits in local emulator. 124 | - Update machine specs in offline API. 125 | - Update pytket-qir version requirement to 0.13. 126 | - Add language option for profile compatible QIR 127 | 128 | ## 0.37.0 (August 2024) 129 | 130 | - Determine maximum classical register width from backend info. 131 | - Permit numpy 2. 132 | - Update pytket_pecos version requirement to 0.1.29. 133 | - Updated pytket version requirement to 1.31. 134 | 135 | ## 0.36.0 (July 2024) 136 | 137 | - Updated pytket version requirement to 1.30. 138 | - Update pytket-qir version requirement to 0.12. 139 | 140 | ## 0.35.0 (June 2024) 141 | 142 | - Update pytket version requirement to 1.29. 143 | - Update pytket_pecos version requirement to 0.1.28. 144 | 145 | ## 0.34.1 (June 2024) 146 | 147 | - Restrict classical registers to a maximum size of 32 (until pytket can 148 | support larger values). 149 | 150 | ## 0.34.0 (June 2024) 151 | 152 | - Update pytket_pecos version requirement to 0.1.27. 153 | - Update Leakage Detection to reuse circuit qubits. 154 | - Update pytket version requirement to 1.28. 155 | - Update pytket-qir version requirement to 0.11. 156 | - Update offline machine specs to match real devices as at 5 June 2024. 157 | 158 | ## 0.33.0 (April 2024) 159 | 160 | - Updated pytket version requirement to 1.27. 161 | - Update pytket_pecos version requirement to 0.1.24. 162 | 163 | ## 0.32.0 (March 2024) 164 | 165 | - Remove `no_opt` and `allow_2q_gate_rebase` options to 166 | {py:meth}`~.QuantinuumBackend.process_circuits` and `submit_program()`, and assume that the submitted 167 | circuit is exactly what is desired to be run. 168 | - Update pytket_pecos version requirement to 0.1.22. 169 | 170 | ## 0.31.0 (March 2024) 171 | 172 | - Updated pytket version requirement to 1.26. 173 | - Update pytket_pecos version requirement to 0.1.19. 174 | - Add methods to enable visibility of Quantinuum H-Series 175 | 176 | operations calendar with and without matplotlib. \* Support TK2 as native gate. \* Update pytket version requirement to 1.26. \* Update pytket-qir version requirement to 0.9. 177 | 178 | ## 0.30.0 (February 2024) 179 | 180 | - Make pytket-qir an automatic dependency. 181 | - Update pytket version requirement to 1.25. 182 | - Update pytket-qir version requirement to 0.5. 183 | - Update pytket_pecos version requirement to 0.1.17. 184 | 185 | ## 0.29.0 (January 2024) 186 | 187 | - Updated pytket_pecos version requirement to 0.1.13. 188 | - Fix handling of results in local emulator with non-default classical 189 | registers. 190 | - Add WASM support to local emulators. 191 | - Add multithreading support to local emulators, via new `multithreading` 192 | keyword argument passed to {py:meth}`~.QuantinuumBackend.process_circuits`. 193 | 194 | ## 0.28.0 (January 2024) 195 | 196 | - Updated pytket version requirement to 1.24. 197 | - Python 3.12 support added, 3.9 dropped. 198 | - pytket_pecos dependency updated to 0.1.9. 199 | 200 | ## 0.27.0 (January 2024) 201 | 202 | - Updated pytket version requirement to 1.23. 203 | - `QuantinuumBackend.cost()` now raises an error if the `syntax_checker` 204 | argument doesn't correspond to the device's reported syntax checker or if it 205 | specifies a device that isn't a syntax checker; and the method returns 0 if 206 | called on syntax-checker backends. 207 | - Add partial support for local emulator backends, if installed with the 208 | `pecos` option. 209 | 210 | ## 0.26.0 (November 2023) 211 | 212 | - Updated pytket version requirement to 1.22. 213 | - Add `QuantinuumConfigCredentialStorage` for caching API tokens in local pytket 214 | configuration file. 215 | - Add an additonal {py:meth}`~pytket.passes.RemoveRedundancies` pass to the default passes for levels 1 and 2 to remove Rz gates before measurement. 216 | 217 | ## 0.25.0 (October 2023) 218 | 219 | - Updated pytket version requirement to 1.21. 220 | 221 | ## 0.24.0 (October 2023) 222 | 223 | - Don't include {py:meth}`~pytket.passes.SimplifyInitial` in default passes; instead make it an option 224 | to {py:meth}`~.QuantinuumBackend.process_circuits`. 225 | - Fix: set default two-qubit gate when compilation config is provided without 226 | specifying one. 227 | 228 | ## 0.23.0 (September 2023) 229 | 230 | - Update pytket-qir version requirement to 0.3. 231 | - Update pytket version requirement to 1.20. 232 | 233 | ## 0.22.0 (September 2023) 234 | 235 | - Update pytket version requirement to 1.19. 236 | 237 | ## 0.21.0 (September 2023) 238 | 239 | - Ensure results retrieval works even with an old-format {py:class}`~pytket.backends.resulthandle.ResultHandle` 240 | (generated by a pre-0.17.0 version of pytket-quantinuum). 241 | - Add properties {py:attr}`~.QuantinuumBackend.default_two_qubit_gate` and 242 | {py:attr}`~.QuantinuumBackend.two_qubit_gate_set` providing the default and supported 243 | two-qubit gates for a device. 244 | - Make `ZZPhase` the default two-qubit gate target on all devices. 245 | - Add {py:class}`~.QuantinuumBackendCompilationConfig` dataclass, which can be passed as 246 | an optional argument when constructing a {py:class}`~.QuantinuumBackend`. Configuration 247 | can be inspected using {py:meth}`~.QuantinuumBackend.get_compilation_config` and 248 | modified using the methods 249 | {py:meth}`~.QuantinuumBackend.set_compilation_config_allow_implicit_swaps` and 250 | {py:meth}`~.QuantinuumBackend.set_compilation_config_target_2qb_gate`. 251 | - Add optional argument `allow_2q_gate_rebase` argument to 252 | {py:meth}`~pytket.backends.backend.Backend.process_circuit`, {py:meth}`~.QuantinuumBackend.process_circuits` and `submit_program()` to 253 | allow the backend to rebase to rebase the circuit to a different two-qubit 254 | gate judged to have better fidelity before being run. The default is to not 255 | allow this. 256 | - Fix handling of multiple classical registers when submitting QIR. 257 | - Change {py:class}`~pytket.backends.resulthandle.ResultHandle` format. (Old {py:class}`~pytket.backends.resulthandle.ResultHandle` objects will continue to 258 | work after upgrading.) 259 | - Fix: Ignore erased scratch bits when constructing {py:class}`~pytket.backends.resulthandle.ResultHandle`. 260 | 261 | ## 0.20.0 (August 2023) 262 | 263 | - Update pytket version requirement to 1.18. 264 | - Add `implicit_swaps` option to 265 | {py:attr}`~.QuantinuumBackend.rebase_pass`, which 266 | can use implicit wire swaps (represented in the circuit qubit permutation) 267 | to help implement some gates when chosen. Defaults to `False`. 268 | - Add `implicit_swaps` option to 269 | {py:attr}`~.QuantinuumBackend.default_compilation_pass`, which 270 | is used in the rebase step. Defaults to `True`. 271 | 272 | ## 0.19.0 (August 2023) 273 | 274 | - Update {py:class}`~pytket.architecture.FullyConnected` Architecture to label Node with "q", matching 275 | compilation by {py:meth}`~pytket.passes.FlattenRelabelRegistersPass`. 276 | 277 | ## 0.18.0 (July 2023) 278 | 279 | - Update pytket version requirement to 1.17. 280 | - Add `leakage_detection` option to {py:meth}`~.QuantinuumBackend.process_circuits` 281 | that automatically modifies Circuits with ancillas for detecting leakage 282 | errors. Also provides a new method `prune_shots_detected_as_leaky` for 283 | removing erroneous shots from {py:class}`~pytket.backends.backendresult.BackendResult`. 284 | 285 | ## 0.17.0 (June 2023) 286 | 287 | - Add {py:class}`~.Language` enum to control language used for circuit submission, with 288 | values `Language.QASM` and `Language.QIR`. 289 | - Renamed `QuantinuumBackend.submit_qasm()` to 290 | `submit_program()`, with a `language` argument. 291 | - Add a `language` kwarg to {py:meth}`~.QuantinuumBackend.process_circuits`, 292 | defaulting to `Language.QASM`. (Support for `Language.QIR` is 293 | experimental and its use is not recommended; a warning will be emitted. You 294 | must install the `pytket-qir` package separately in order to use this 295 | feature.) 296 | - Use "q" instead of "node" as the name of the single qubit register in compiled 297 | circuits. 298 | - Updated pytket version requirement to 1.16. 299 | 300 | ## 0.16.0 (May 2023) 301 | 302 | - Updated pytket version requirement to 1.15. 303 | - cost function now takes the same kwargs as process_circuits 304 | - add check for the number of classical registers to the backend 305 | - add `get_partial_result()` method to {py:class}`~.QuantinuumBackend`. 306 | - add `Rxxyyzz` gate support. 307 | 308 | ## 0.15.0 (April 2023) 309 | 310 | - Darkmode added to the documentation 311 | - Updated pytket version requirement to 1.13.2 312 | - Default compilation passes updated to correctly track initial and final maps during compilation 313 | 314 | ## 0.14.0 (March 2023) 315 | 316 | - Use default `Node` register for flattening in default compilation pass. 317 | - Prefer `ZZPhase` to `ZZMax` gates if available. 318 | - Updated pytket version requirement to 1.13. 319 | 320 | ## 0.13.0 (January 2023) 321 | 322 | - Drop support for Python 3.8; add support for 3.11. 323 | - The backend now works in threads other than the main. 324 | - Updated pytket version requirement to 1.11. 325 | 326 | ## 0.12.0 (December 2022) 327 | 328 | - Updated pytket version requirement to 1.10. 329 | - Default compilation pass update to flatten registers 330 | 331 | ## 0.11.0 (November 2022) 332 | 333 | - Updated pytket version requirement to 1.9. 334 | - Add optional `no_opt` argument to {py:meth}`~.QuantinuumBackend.process_circuits` and 335 | `submit_qasm()`, requesting no optimization. 336 | - Change default optimization level in 337 | {py:attr}`~.QuantinuumBackend.default_compilation_pass` to 2. 338 | - {py:attr}`~.QuantinuumBackend.default_compilation_pass` now flattens qubit registers when compiling Circuits. 339 | 340 | ## 0.10.0 (November 2022) 341 | 342 | - Break up `pytket` internal scratch registers if their widths exceed limit. 343 | - Updated pytket version requirement to 1.8. 344 | 345 | ## 0.9.0 (October 2022) 346 | 347 | - Add `session` parameter to {py:class}`~.QuantinuumAPI`. Creates a new session 348 | if `None` is provided. 349 | - Add facility to specify default `options` paramater to 350 | {py:meth}`~.QuantinuumBackend.process_circuits` and `submit_qasm()` when constructing backend, and 351 | include this information in {py:attr}`~.QuantinuumBackend.backend_info`. 352 | - Updated pytket version requirement to 1.7. 353 | 354 | ## 0.8.0 (September 2022) 355 | 356 | - Add `options` parameter to {py:meth}`~.QuantinuumBackend.process_circuits` and `submit_qasm()`. 357 | - Updated pytket version requirement to 1.6. 358 | 359 | ## 0.7.0 (August 2022) 360 | 361 | - Add new {py:class}`~.QuantinuumAPIOffline` for allowing usage of the backend without API calls. 362 | - New `api_handler` parameter for {py:class}`~.QuantinuumBackend`, allowing to choose 363 | online or offline options. Default value is the standard online api. 364 | - Updated pytket version requirement to 1.5. 365 | 366 | ## 0.6.0 (July 2022) 367 | 368 | - Changed batching interface: {py:meth}`~.QuantinuumBackend.process_circuits` no longer batches, use 369 | `start_batching` and `add_to_batch` methods to explicitly start and append to 370 | batches. 371 | - New `submit_qasm` backend method to enable direct submission of a QASM program. 372 | 373 | ## 0.5.0 (July 2022) 374 | 375 | - Updated pytket version requirement to 1.4. 376 | - Add support for multi-factor authentication and microsoft federated login. 377 | 378 | ## 0.4.0 (June 2022) 379 | 380 | - Add wasm support 381 | - Add support for `OpType.CopyBits` and `OpType.ClassicalExpBox` in {py:class}`~.QuantinuumBackend` 382 | - Updated pytket version requirement to 1.3. 383 | - Add optional argument `group` to {py:class}`~.QuantinuumBackend` 384 | 385 | ## 0.3.1 (May 2022) 386 | 387 | - Updated to pyjwt 2.4. This fixes a potential security vulnerability 388 | (CVE-2022-29217). 389 | 390 | ## 0.3.0 (May 2022) 391 | 392 | - `QuantinuumBackend.cost_estimate` deprecated, new `QuantinuumBackend.cost()` 393 | method now uses the syntax checker devices to directly return the cost. 394 | - Updated pytket version requirement to 1.2. 395 | 396 | ## 0.2.0 (April 2022) 397 | 398 | - Updated pytket version requirement to 1.1. 399 | 400 | ## 0.1.2 (April 2022) 401 | 402 | - Fix batch handling in {py:meth}`~.QuantinuumBackend.process_circuits`. 403 | 404 | ## 0.1.1 (March 2022) 405 | 406 | - Update device names. 407 | 408 | ## 0.1.0 (March 2022) 409 | 410 | - Module renamed from `pytket.extensions.honeywell` to 411 | `pytket.extensions.quantinumm`, with corresponding name changes throughout. 412 | - Simplify authentication: use `QuantinuumBackend.login()` to log in once per session. 413 | - Updated pytket version requirement to 1.0. 414 | 415 | Old changelog for "pytket-honeywell": 416 | 417 | ### 0.21.0 (February 2022) 418 | 419 | - Updated pytket version requirement to 0.19. 420 | - Drop support for Python 3.7; add support for 3.10. 421 | 422 | ### 0.20.0 (January 2022) 423 | 424 | - Added optional `group` field to circuit submission. 425 | 426 | ### 0.19.0 (January 2022) 427 | 428 | - Updated pytket version requirement to 0.18. 429 | 430 | ### 0.18.0 (November 2021) 431 | 432 | - Updated pytket version requirement to 0.17. 433 | 434 | ### 0.17.0 (October 2021) 435 | 436 | - Updated pytket version requirement to 0.16. 437 | - Renamed `HoneywellBackend.available_devices` to `_available_devices` so as 438 | not to conflict with abstract {py:class}`~pytket.backends.backend.Backend` method. 439 | 440 | ### 0.16.0 (September 2021) 441 | 442 | - Updated pytket version requirement to 0.15. 443 | 444 | ### 0.15.0 (September 2021) 445 | 446 | - Updated pytket version requirement to 0.14. 447 | 448 | ### 0.14.0 (August 2021) 449 | 450 | - Support new Honeywell simulator options in `HoneywellBackend`: 451 | "simulator" for simulator type, and "noisy_simulation" to toggle simulations 452 | with and without error models. 453 | - Device name no longer optional on `HoneywellBackend` construction. 454 | 455 | ### 0.13.0 (July 2021) 456 | 457 | - Updated pytket version requirement to 0.13. 458 | 459 | ### 0.12.0 (June 2021) 460 | 461 | - Updated pytket version requirement to 0.12. 462 | 463 | ### 0.11.0 (May 2021) 464 | 465 | - Updated pytket version requirement to 0.11. 466 | 467 | ### 0.10.0 (May 2021) 468 | 469 | - Contextual optimisation added to default compilation passes (except at optimisation level 0). 470 | -------------------------------------------------------------------------------- /tests/integration/backend_test.py: -------------------------------------------------------------------------------- 1 | # Copyright Quantinuum 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 json 16 | import os 17 | from collections import Counter 18 | from collections.abc import Callable # pylint: disable=unused-import 19 | from pathlib import Path 20 | from typing import Any, cast 21 | 22 | import hypothesis.strategies as st 23 | import numpy as np 24 | import pytest 25 | from hypothesis import given 26 | from hypothesis.strategies._internal import SearchStrategy 27 | from pytket.circuit import ( 28 | Bit, 29 | Circuit, 30 | Conditional, 31 | Node, 32 | OpType, 33 | Qubit, 34 | if_not_bit, 35 | reg_eq, 36 | reg_geq, 37 | reg_gt, 38 | reg_leq, 39 | reg_lt, 40 | reg_neq, 41 | ) 42 | from pytket.circuit.clexpr import wired_clexpr_from_logic_exp 43 | from pytket.passes import BasePass, SequencePass 44 | from pytket.passes.resizeregpass import _gen_scratch_transformation 45 | from pytket.predicates import CompilationUnit 46 | from pytket.wasm import WasmFileHandler 47 | 48 | from pytket.extensions.quantinuum import ( 49 | QuantinuumBackend, 50 | QuantinuumBackendCompilationConfig, 51 | have_pecos, 52 | prune_shots_detected_as_leaky, 53 | ) 54 | from pytket.extensions.quantinuum.backends.quantinuum import _ALL_GATES, MAX_C_REG_WIDTH 55 | 56 | 57 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 58 | def test_bell() -> None: 59 | b = QuantinuumBackend("H2-1LE") 60 | c = Circuit(2, 2, "test 2") 61 | c.H(0) 62 | c.CX(0, 1) 63 | c.measure_all() 64 | c = b.get_compiled_circuit(c) 65 | n_shots = 10 66 | shots = b.run_circuit(c, n_shots=n_shots).get_shots() 67 | assert all(q[0] == q[1] for q in shots) 68 | 69 | 70 | def test_default_pass() -> None: 71 | b = QuantinuumBackend("H2-1") 72 | for ol in range(3): 73 | comp_pass = b.default_compilation_pass(ol) 74 | c = Circuit(3, 3) 75 | q0 = Qubit("test0", 5) 76 | q1 = Qubit("test1", 6) 77 | c.add_qubit(q0) 78 | c.H(q0) 79 | c.H(0) 80 | c.CX(0, 1) 81 | c.CSWAP(1, 0, 2) 82 | c.ZZPhase(0.84, 2, 0) 83 | c.measure_all() 84 | c.add_qubit(q1) 85 | cu = CompilationUnit(c) 86 | comp_pass.apply(cu) 87 | # 5 qubits added to Circuit, one is removed when flattening registers 88 | assert cu.circuit.qubits == [ 89 | Node("q", 0), 90 | Node("q", 1), 91 | Node("q", 2), 92 | Node("q", 3), 93 | ] 94 | assert cu.initial_map[Qubit(0)] == Node("q", 0) 95 | assert cu.initial_map[Qubit(1)] == Node("q", 1) 96 | assert cu.initial_map[Qubit(2)] == Node("q", 2) 97 | assert cu.initial_map[q0] == Node("q", 3) 98 | assert cu.initial_map[q1] == q1 99 | for pred in b.required_predicates: 100 | assert pred.verify(cu.circuit) 101 | 102 | 103 | @st.composite 104 | def circuits( 105 | draw: Callable[[SearchStrategy[Any]], Any], 106 | n_qubits: SearchStrategy[int] = st.integers(min_value=2, max_value=6), # noqa: B008 107 | depth: SearchStrategy[int] = st.integers(min_value=1, max_value=100), # noqa: B008 108 | ) -> Circuit: 109 | total_qubits = draw(n_qubits) 110 | circuit = Circuit(total_qubits, total_qubits) 111 | for _ in range(draw(depth)): 112 | gate = draw(st.sampled_from(list(_ALL_GATES))) 113 | control = draw(st.integers(min_value=0, max_value=total_qubits - 1)) 114 | if gate == OpType.ZZMax: 115 | target = draw( 116 | st.integers(min_value=0, max_value=total_qubits - 1).filter( 117 | lambda x: x != control # noqa: B023 118 | ) 119 | ) 120 | circuit.add_gate(gate, [control, target]) 121 | elif gate == OpType.Measure: 122 | circuit.add_gate(gate, [control, control]) 123 | circuit.add_gate(OpType.Reset, [control]) 124 | elif gate == OpType.Rz: 125 | param = draw(st.floats(min_value=0, max_value=2)) 126 | circuit.add_gate(gate, [param], [control]) 127 | elif gate == OpType.PhasedX: 128 | param1 = draw(st.floats(min_value=0, max_value=2)) 129 | param2 = draw(st.floats(min_value=0, max_value=2)) 130 | circuit.add_gate(gate, [param1, param2], [control]) 131 | circuit.measure_all() 132 | 133 | return circuit 134 | 135 | 136 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 137 | def test_classical() -> None: 138 | # circuit to cover capabilities covered in example notebook 139 | c = Circuit(1, name="test_classical") 140 | a = c.add_c_register("a", 8) 141 | b = c.add_c_register("b", 10) 142 | d = c.add_c_register("d", 10) 143 | 144 | c.add_c_setbits([True], [a[0]]) 145 | c.add_c_setbits([False, True] + [False] * 6, a) # type: ignore 146 | c.add_c_setbits([True, True] + [False] * 8, b) # type: ignore 147 | 148 | c.add_c_setreg(23, a) 149 | c.add_c_copyreg(a, b) 150 | 151 | c.add_clexpr(*wired_clexpr_from_logic_exp(a + b, d.to_list())) 152 | c.add_clexpr(*wired_clexpr_from_logic_exp(a - b, d.to_list())) 153 | c.add_clexpr(*wired_clexpr_from_logic_exp(a << 1, a.to_list())) 154 | c.add_clexpr(*wired_clexpr_from_logic_exp(a >> 1, b.to_list())) 155 | 156 | c.X(0, condition=reg_eq(a ^ b, 1)) 157 | c.X(0, condition=(a[0] ^ b[0])) 158 | c.X(0, condition=reg_eq(a & b, 1)) 159 | c.X(0, condition=reg_eq(a | b, 1)) 160 | 161 | c.X(0, condition=a[0]) 162 | c.X(0, condition=reg_neq(a, 1)) 163 | c.X(0, condition=if_not_bit(a[0])) 164 | c.X(0, condition=reg_gt(a, 1)) 165 | c.X(0, condition=reg_lt(a, 1)) 166 | c.X(0, condition=reg_geq(a, 1)) 167 | c.X(0, condition=reg_leq(a, 1)) 168 | c.Phase(0, condition=a[0]) 169 | 170 | c.measure_all() 171 | 172 | backend = QuantinuumBackend("H2-1LE") 173 | 174 | c = backend.get_compiled_circuit(c) 175 | assert backend.run_circuit(c, n_shots=10).get_counts() 176 | 177 | 178 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 179 | def test_postprocess() -> None: 180 | b = QuantinuumBackend("H2-1LE") 181 | assert b.supports_contextual_optimisation 182 | c = Circuit(2, 2) 183 | c.add_gate(OpType.PhasedX, [1, 1], [0]) 184 | c.add_gate(OpType.PhasedX, [1, 1], [1]) 185 | c.add_gate(OpType.ZZMax, [0, 1]) 186 | c.measure_all() 187 | c = b.get_compiled_circuit(c) 188 | h = b.process_circuit(c, n_shots=10, postprocess=True) 189 | ppcirc = Circuit.from_dict(json.loads(cast("str", h[1]))) 190 | ppcmds = ppcirc.get_commands() 191 | assert len(ppcmds) > 0 192 | assert all(ppcmd.op.type == OpType.ClassicalTransform for ppcmd in ppcmds) 193 | r = b.get_result(h) 194 | shots = r.get_shots() 195 | assert len(shots) == 10 196 | 197 | 198 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 199 | def test_leakage_detection() -> None: 200 | b = QuantinuumBackend("H2-1LE") 201 | c = Circuit(2, 2).H(0).CZ(0, 1).Measure(0, 0).Measure(1, 1) 202 | 203 | with pytest.raises(ValueError): 204 | b.process_circuit( 205 | c, n_shots=10, leakage_detection=True, n_leakage_detection_qubits=1000 206 | ) 207 | h = b.process_circuit(c, n_shots=10, leakage_detection=True) 208 | r = b.get_result(h) 209 | assert len(r.c_bits) == 4 210 | assert sum(r.get_counts().values()) == 10 211 | r_discarded = prune_shots_detected_as_leaky(r) 212 | assert len(r_discarded.c_bits) == 2 213 | assert sum(r_discarded.get_counts().values()) == 10 214 | 215 | 216 | @given( 217 | n_shots=st.integers(min_value=1, max_value=10), 218 | n_bits=st.integers(min_value=0, max_value=10), 219 | ) 220 | def test_shots_bits_edgecases(n_shots: int, n_bits: int) -> None: 221 | quantinuum_backend = QuantinuumBackend("H2-1LE", machine_debug=True) 222 | c = Circuit(n_bits, n_bits) 223 | 224 | h = quantinuum_backend.process_circuit(c, n_shots) 225 | res = quantinuum_backend.get_result(h) 226 | 227 | correct_shots = np.zeros((n_shots, n_bits), dtype=int) 228 | correct_shape = (n_shots, n_bits) 229 | correct_counts = Counter({(0,) * n_bits: n_shots}) 230 | # BackendResult 231 | assert np.array_equal(res.get_shots(), correct_shots) 232 | assert res.get_shots().shape == correct_shape 233 | assert res.get_counts() == correct_counts 234 | 235 | # Direct 236 | res = quantinuum_backend.run_circuit(c, n_shots=n_shots) 237 | assert np.array_equal(res.get_shots(), correct_shots) 238 | assert res.get_shots().shape == correct_shape 239 | assert res.get_counts() == correct_counts 240 | 241 | 242 | def test_retrieve_available_devices() -> None: 243 | backend_infos = QuantinuumBackend.available_devices() 244 | assert len(backend_infos) > 0 245 | assert all( 246 | {OpType.TK2, OpType.ZZMax, OpType.ZZPhase} & backend_info.gate_set 247 | for backend_info in backend_infos 248 | ) 249 | 250 | 251 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 252 | def test_submission_with_group() -> None: 253 | b = QuantinuumBackend("H2-1LE") 254 | c = Circuit(2, 2, "test 2") 255 | c.H(0) 256 | c.CX(0, 1) 257 | c.measure_all() 258 | c = b.get_compiled_circuit(c) 259 | n_shots = 10 260 | shots = b.run_circuit( 261 | c, 262 | n_shots=n_shots, 263 | group=os.getenv("PYTKET_REMOTE_QUANTINUUM_GROUP", default="DEFAULT"), 264 | ).get_shots() 265 | assert all(q[0] == q[1] for q in shots) 266 | 267 | 268 | def test_zzphase_support_opti2() -> None: 269 | backend = QuantinuumBackend("H2-1") 270 | c = Circuit(3, 3, "test rzz synthesis") 271 | c.H(0) 272 | c.CX(0, 2) 273 | c.Rz(0.2, 2) 274 | c.CX(0, 2) 275 | c.measure_all() 276 | c0 = backend.get_compiled_circuit(c, 2) 277 | 278 | assert c0.n_gates_of_type(backend.default_two_qubit_gate) == 1 279 | 280 | 281 | def test_prefer_zzphase() -> None: 282 | # We should prefer small-angle ZZPhase to alternative ZZMax decompositions 283 | backend = QuantinuumBackend("H2-1") 284 | c = ( 285 | Circuit(2) 286 | .H(0) 287 | .H(1) 288 | .ZZPhase(0.1, 0, 1) 289 | .Rx(0.2, 0) 290 | .Ry(0.3, 1) 291 | .ZZPhase(0.1, 0, 1) 292 | .H(0) 293 | .H(1) 294 | .measure_all() 295 | ) 296 | c0 = backend.get_compiled_circuit(c) 297 | if backend.default_two_qubit_gate == OpType.ZZPhase: 298 | assert c0.n_gates_of_type(OpType.ZZPhase) == 2 299 | elif backend.default_two_qubit_gate == OpType.ZZMax: 300 | assert c0.n_gates_of_type(OpType.ZZMax) == 2 301 | else: 302 | assert backend.default_two_qubit_gate == OpType.TK2 303 | assert c0.n_gates_of_type(OpType.TK2) == 1 304 | 305 | 306 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 307 | def test_wasm_qa() -> None: 308 | wasfile = WasmFileHandler(str(Path(__file__).parent.parent / "wasm" / "add1.wasm")) 309 | c = Circuit(1) 310 | c.name = "test_wasm" 311 | a = c.add_c_register("a", 8) 312 | c.add_wasm_to_reg("add_one", wasfile, [a], [a]) 313 | c.measure_all() 314 | 315 | b = QuantinuumBackend("H2-1LE") 316 | 317 | c = b.get_compiled_circuit(c) 318 | h = b.process_circuits([c], n_shots=10, wasm_file_handler=wasfile)[0] 319 | 320 | r = b.get_result(h) 321 | shots = r.get_shots() 322 | assert len(shots) == 10 323 | 324 | 325 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 326 | def test_wasm() -> None: 327 | wasfile = WasmFileHandler(str(Path(__file__).parent.parent / "wasm" / "add1.wasm")) 328 | c = Circuit(1, 1) 329 | c.name = "test_wasm" 330 | a = c.add_c_register("a", 8) 331 | c.add_wasm_to_reg("add_one", wasfile, [a], [a]) 332 | c.measure_all() 333 | 334 | b = QuantinuumBackend("H2-1LE") 335 | 336 | c = b.get_compiled_circuit(c) 337 | h = b.process_circuits([c], n_shots=10, wasm_file_handler=wasfile)[0] 338 | 339 | r = b.get_result(h) 340 | shots = r.get_shots() 341 | assert len(shots) == 10 342 | 343 | 344 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 345 | def test_options() -> None: 346 | # Unrecognized options are ignored 347 | c0 = Circuit(1).H(0).measure_all() 348 | b = QuantinuumBackend("H2-1LE") 349 | c = b.get_compiled_circuit(c0, 0) 350 | h = b.process_circuits([c], n_shots=1, options={"ignoreme": 0}) 351 | r = b.get_results(h)[0] 352 | shots = r.get_shots() 353 | assert len(shots) == 1 354 | assert len(shots[0]) == 1 355 | 356 | 357 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 358 | def test_tk2() -> None: 359 | c0 = ( 360 | Circuit(2) 361 | .XXPhase(0.1, 0, 1) 362 | .YYPhase(0.2, 0, 1) 363 | .ZZPhase(0.3, 0, 1) 364 | .measure_all() 365 | ) 366 | b = QuantinuumBackend("H2-1LE") 367 | b.set_compilation_config_target_2qb_gate(OpType.TK2) 368 | c = b.get_compiled_circuit(c0, 2) 369 | h = b.process_circuit(c, n_shots=1) 370 | r = b.get_result(h) 371 | shots = r.get_shots() 372 | assert len(shots) == 1 373 | assert len(shots[0]) == 2 374 | 375 | 376 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 377 | def test_wasm_collatz() -> None: 378 | wasmfile = WasmFileHandler( 379 | str(Path(__file__).parent.parent / "wasm" / "collatz.wasm") 380 | ) 381 | c = Circuit(8) 382 | a = c.add_c_register("a", 8) 383 | b = c.add_c_register("b", 8) 384 | 385 | # Use Hadamards to set "a" register to a random value. 386 | for i in range(8): 387 | c.H(i) 388 | c.Measure(Qubit(i), Bit("a", i)) 389 | # Compute the value of the Collatz function on this value. 390 | c.add_wasm_to_reg("collatz", wasmfile, [a], [b]) 391 | 392 | backend = QuantinuumBackend("H2-1LE") 393 | 394 | c = backend.get_compiled_circuit(c) 395 | h = backend.process_circuit(c, n_shots=10, wasm_file_handler=wasmfile) 396 | 397 | r = backend.get_result(h) 398 | shots = r.get_shots() 399 | 400 | def to_int(C: np.ndarray) -> int: 401 | assert len(C) == 8 402 | return sum(pow(2, i) * int(C[i]) for i in range(8)) 403 | 404 | def collatz(n: int) -> int: 405 | if n == 0: 406 | return 0 407 | m = 0 408 | while n != 1: 409 | n = (3 * n + 1) // 2 if n % 2 == 1 else n // 2 410 | m += 1 411 | return m 412 | 413 | for shot in shots: 414 | n, m = to_int(shot[:8]), to_int(shot[8:16]) 415 | assert collatz(n) == m 416 | 417 | 418 | # FIXME: Bug in pecos? 419 | @pytest.mark.xfail 420 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 421 | def test_wasm_state() -> None: 422 | wasmfile = WasmFileHandler( 423 | str(Path(__file__).parent.parent / "wasm" / "state.wasm") 424 | ) 425 | c = Circuit(8) 426 | a = c.add_c_register("a", 8).to_list() # measurement results 427 | b = c.add_c_register("b", 4) # final count 428 | s = c.add_c_register("s", 1) # scratch bit 429 | 430 | # Use Hadamards to set "a" register to random values. 431 | for i in range(8): 432 | c.H(i) 433 | c.Measure(Qubit(i), a[i]) 434 | # Count the number of 1s in the "a" register and store in the "b" register. 435 | c.add_wasm_to_reg("set_c", wasmfile, [s], []) # set c to zero 436 | for i in range(8): 437 | # Copy a[i] to s 438 | c.add_c_copybits([a[i]], [Bit("s", 0)]) 439 | # Conditionally increment the counter 440 | c.add_wasm_to_reg("conditional_increment_c", wasmfile, [s], []) 441 | # Put the counter into "b" 442 | c.add_wasm_to_reg("get_c", wasmfile, [], [b]) 443 | 444 | backend = QuantinuumBackend("H2-1LE") 445 | 446 | c = backend.get_compiled_circuit(c) 447 | h = backend.process_circuit(c, n_shots=10, wasm_file_handler=wasmfile) 448 | 449 | r = backend.get_result(h) 450 | shots = r.get_shots() 451 | 452 | def to_int(C: np.ndarray) -> int: 453 | assert len(C) == 4 454 | return sum(pow(2, i) * C[i] for i in range(4)) 455 | 456 | for shot in shots: 457 | a_count = sum(shot[:8]) 458 | b_count = to_int(shot[8:12]) 459 | assert a_count == b_count 460 | 461 | 462 | def test_default_2q_gate() -> None: 463 | # https://github.com/CQCL/pytket-quantinuum/issues/250 464 | config = QuantinuumBackendCompilationConfig(allow_implicit_swaps=False) 465 | b = QuantinuumBackend("H2-1", compilation_config=config) 466 | c = Circuit(2).H(0).CX(0, 1).measure_all() 467 | c1 = b.get_compiled_circuit(c) 468 | assert any(cmd.op.type == b.default_two_qubit_gate for cmd in c1) 469 | 470 | 471 | # https://github.com/CQCL/pytket-quantinuum/issues/265 472 | def test_Rz_removal_before_measurements() -> None: 473 | backend = QuantinuumBackend("H2-1", machine_debug=True) 474 | # Circuit will contain an Rz gate if RemoveRedundancies 475 | # isn't applied after SquashRzPhasedX 476 | circuit = Circuit(2).H(0).Rz(0.75, 0).CX(0, 1).measure_all() 477 | 478 | for optimisation_level in (1, 2): 479 | compiled_circuit = backend.get_compiled_circuit( 480 | circuit, optimisation_level=optimisation_level 481 | ) 482 | assert backend.valid_circuit(compiled_circuit) 483 | assert compiled_circuit.n_gates_of_type(OpType.Rz) == 0 484 | 485 | 486 | # https://github.com/CQCL/pytket-quantinuum/issues/263 487 | @pytest.mark.skipif(not have_pecos(), reason="pecos not installed") 488 | def test_noiseless_emulation() -> None: 489 | backend = QuantinuumBackend("H2-1LE") 490 | c = Circuit(2).H(0).CX(0, 1).measure_all() 491 | c1 = backend.get_compiled_circuit(c) 492 | h = backend.process_circuit(c1, n_shots=100, noisy_simulation=False) 493 | r = backend.get_result(h) 494 | counts = r.get_counts() 495 | assert all(x0 == x1 for x0, x1 in counts) 496 | 497 | 498 | def test_optimisation_level_3_compilation() -> None: 499 | b = QuantinuumBackend("H2-1") 500 | 501 | c = Circuit(6) 502 | c.add_barrier([0, 1, 2, 3, 4, 5]) 503 | for _ in range(6): 504 | for i in range(4): 505 | for j in range(i + 1, 4): 506 | c.CX(i, j) 507 | c.Rz(0.23, j) 508 | c.S(j) 509 | c.H(i) 510 | 511 | compiled_2 = b.get_compiled_circuit(c, 2) 512 | compiled_3 = b.get_compiled_circuit(c, 3) 513 | 514 | assert compiled_2.n_2qb_gates() == 36 515 | assert compiled_2.n_gates == 98 516 | assert compiled_2.depth() == 45 517 | assert compiled_3.n_2qb_gates() == 31 518 | assert compiled_3.n_gates == 93 519 | assert compiled_3.depth() == 49 520 | 521 | 522 | def test_no_phase_ops() -> None: 523 | b = QuantinuumBackend("H2-1") 524 | 525 | c = ( 526 | Circuit(3, 3) 527 | .H(0) 528 | .Measure(0, 0) 529 | .H(1) 530 | .CX(1, 2, condition_bits=[0], condition_value=1) 531 | .Measure(1, 1) 532 | .Measure(2, 2) 533 | ) 534 | c1 = b.get_compiled_circuit(c) 535 | for cmd in c1.get_commands(): 536 | op = cmd.op 537 | typ = op.type 538 | assert typ != OpType.Phase 539 | if typ == OpType.Conditional: 540 | assert isinstance(op, Conditional) 541 | assert op.op.type != OpType.Phase 542 | 543 | 544 | def test_default_pass_serialization() -> None: 545 | h11e_backend = QuantinuumBackend("H2-1", machine_debug=True) 546 | 547 | for opt_level in range(4): 548 | default_pass = h11e_backend.default_compilation_pass(opt_level) 549 | original_pass_dict = default_pass.to_dict() 550 | reconstructed_pass = BasePass.from_dict( 551 | original_pass_dict, 552 | {"resize scratch bits": _gen_scratch_transformation(MAX_C_REG_WIDTH)}, 553 | ) 554 | assert isinstance(reconstructed_pass, SequencePass) 555 | assert original_pass_dict == reconstructed_pass.to_dict() 556 | 557 | 558 | def test_pass_from_info() -> None: 559 | be = QuantinuumBackend("H2-1") 560 | info = be.backend_info 561 | assert info is not None 562 | actual_pass = QuantinuumBackend.pass_from_info(info) 563 | expected_pass = be.default_compilation_pass() 564 | assert actual_pass.to_dict() == expected_pass.to_dict() 565 | --------------------------------------------------------------------------------