From 3155fca07a3eac7ef034b0af097ad08ff74a8f8b Mon Sep 17 00:00:00 2001 From: Pablo Andres-Martinez <104848389+PabloAndresCQ@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:24:13 +0100 Subject: [PATCH 01/15] Changed the link to old pytket-cutensornet docs (#1495) --- pytket/docs/extensions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytket/docs/extensions.rst b/pytket/docs/extensions.rst index 8a421d87af..52f83aaa9a 100644 --- a/pytket/docs/extensions.rst +++ b/pytket/docs/extensions.rst @@ -132,7 +132,7 @@ Other pytket-qir pytket-qiskit pytket-quantinuum - pytket-cutensornet + pytket-cutensornet pytket-qulacs pytket-qujax pytket-stim From 883219fa54bb7af6191f45ce76d59bdeb3a09428 Mon Sep 17 00:00:00 2001 From: Pablo Andres-Martinez <104848389+PabloAndresCQ@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:30:43 +0100 Subject: [PATCH 02/15] Changed the link to new pytket-cutensornet docs (#1507) --- pytket/docs/extensions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytket/docs/extensions.rst b/pytket/docs/extensions.rst index 52f83aaa9a..8a421d87af 100644 --- a/pytket/docs/extensions.rst +++ b/pytket/docs/extensions.rst @@ -132,7 +132,7 @@ Other pytket-qir pytket-qiskit pytket-quantinuum - pytket-cutensornet + pytket-cutensornet pytket-qulacs pytket-qujax pytket-stim From d575477873e81c74d016b5b90dd4b990f5cddd80 Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:54:41 +0100 Subject: [PATCH 03/15] docs: update navbar config and theming submodule (#1523) (#1524) * update navbar config * use latest quantinuum-sphinx * fix some local build warnings --- pytket/docs/_static/nav-config.js | 83 +++++++++++++++---------------- pytket/docs/conf.py | 3 ++ pytket/docs/quantinuum-sphinx | 2 +- 3 files changed, 43 insertions(+), 45 deletions(-) diff --git a/pytket/docs/_static/nav-config.js b/pytket/docs/_static/nav-config.js index 6f975c7055..7da76ddfbf 100644 --- a/pytket/docs/_static/nav-config.js +++ b/pytket/docs/_static/nav-config.js @@ -1,46 +1,41 @@ -const navConfig = { - +const navConfig = { + "navTextLinks": [ - { - "title": "API Docs", - "href": "../api-docs", - "pathMatch": "somewhere", - }, - { - "title": "Examples", - "href": "../examples", - "pathMatch": "somewhere", - }, - { - "title": "Blog", - "href": "../blog/", - "pathMatch": "somewhere", - }, - { - "title": "User Manual", - "href": "../user-manual", - "pathMatch": "somewhere", - }, -], -"navProductName": "TKET", -"navIconLinks": [ - { - "title": "TKET Github", - "href": "https://github.com/CQCL/tket", - "pathMatch": "somewhere", - "iconImageURL": "_static/assets/github.svg", - }, - { - "title": "TKET Slack Channel", - "href": "https://tketusers.slack.com/", - "pathMatch": "somewhere", - "iconImageURL": "_static/assets/slack.svg", - }, - { - "title": "TKET Stack Exchange", - "href": "https://quantumcomputing.stackexchange.com/questions/tagged/pytket", - "pathMatch": "somewhere", - "iconImageURL": "_static/assets/stack.svg", - }, -], + { + "title": "API Docs", + "href": "../../api-docs", + "pathMatch": "somewhere", + }, + { + "title": "Blog", + "href": "../../blog/", + "pathMatch": "somewhere", + }, + { + "title": "User Guide", + "href": "../../user-guide", + "pathMatch": "somewhere", + }, + ], + "navProductName": "TKET", + "navIconLinks": [ + { + "title": "TKET Github", + "href": "https://github.com/CQCL/tket", + "pathMatch": "somewhere", + "iconImageURL": "/_static/assets/github.svg", + }, + { + "title": "TKET Slack Channel", + "href": "https://tketusers.slack.com/", + "pathMatch": "somewhere", + "iconImageURL": "/_static/assets/slack.svg", + }, + { + "title": "TKET Stack Exchange", + "href": "https://quantumcomputing.stackexchange.com/questions/tagged/pytket", + "pathMatch": "somewhere", + "iconImageURL": "/_static/assets/stack.svg", + }, + ], } diff --git a/pytket/docs/conf.py b/pytket/docs/conf.py index 21bc4c78d1..56111227a1 100644 --- a/pytket/docs/conf.py +++ b/pytket/docs/conf.py @@ -96,6 +96,9 @@ # documentation. # + +exclude_patterns = ["build/jupyter_execute/*", ".venv/*"] + html_theme_options = {} # Custom sidebar templates, must be a dictionary that maps document names diff --git a/pytket/docs/quantinuum-sphinx b/pytket/docs/quantinuum-sphinx index 544cf64c04..160b297794 160000 --- a/pytket/docs/quantinuum-sphinx +++ b/pytket/docs/quantinuum-sphinx @@ -1 +1 @@ -Subproject commit 544cf64c042062bbef5c6aecd448cc86058e98d1 +Subproject commit 160b297794d5af8832b7d7085707a9bcc43c93d3 From ba9efe34d29360a5103a7711107f4389e13550bf Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:30:42 +0100 Subject: [PATCH 04/15] bump quantinuum-sphinx (#1526) --- pytket/docs/quantinuum-sphinx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytket/docs/quantinuum-sphinx b/pytket/docs/quantinuum-sphinx index 160b297794..33287684e5 160000 --- a/pytket/docs/quantinuum-sphinx +++ b/pytket/docs/quantinuum-sphinx @@ -1 +1 @@ -Subproject commit 160b297794d5af8832b7d7085707a9bcc43c93d3 +Subproject commit 33287684e58d2b38ab5867c8e79bcd43460676df From d89ed928c35101904d2111b63cfcae4639832c8b Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:18:36 +0100 Subject: [PATCH 05/15] correct navbar links (#1527) (#1528) --- pytket/docs/_static/nav-config.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pytket/docs/_static/nav-config.js b/pytket/docs/_static/nav-config.js index 7da76ddfbf..b83d723c87 100644 --- a/pytket/docs/_static/nav-config.js +++ b/pytket/docs/_static/nav-config.js @@ -3,17 +3,17 @@ const navConfig = { "navTextLinks": [ { "title": "API Docs", - "href": "../../api-docs", + "href": "../api-docs", "pathMatch": "somewhere", }, { "title": "Blog", - "href": "../../blog/", + "href": "../blog/", "pathMatch": "somewhere", }, { "title": "User Guide", - "href": "../../user-guide", + "href": "../user-guide", "pathMatch": "somewhere", }, ], @@ -23,19 +23,19 @@ const navConfig = { "title": "TKET Github", "href": "https://github.com/CQCL/tket", "pathMatch": "somewhere", - "iconImageURL": "/_static/assets/github.svg", + "iconImageURL": "_static/assets/github.svg", }, { "title": "TKET Slack Channel", "href": "https://tketusers.slack.com/", "pathMatch": "somewhere", - "iconImageURL": "/_static/assets/slack.svg", + "iconImageURL": "_static/assets/slack.svg", }, { "title": "TKET Stack Exchange", "href": "https://quantumcomputing.stackexchange.com/questions/tagged/pytket", "pathMatch": "somewhere", - "iconImageURL": "/_static/assets/stack.svg", + "iconImageURL": "_static/assets/stack.svg", }, ], } From e14e7cc31378a20de726f12d10aa9a0f78e634b3 Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Mon, 19 Aug 2024 10:24:42 +0100 Subject: [PATCH 06/15] docs: Add pytket-azure to list of extensions (#1540) (#1543) --- pytket/docs/extensions.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/pytket/docs/extensions.rst b/pytket/docs/extensions.rst index 8a421d87af..7dba547c7f 100644 --- a/pytket/docs/extensions.rst +++ b/pytket/docs/extensions.rst @@ -121,6 +121,7 @@ Other :maxdepth: 0 pytket-aqt + pytket-azure pytket-braket pytket-cirq pytket-iqm From a70776836b9194d9679517cbe7ef499f7d182d50 Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Fri, 6 Sep 2024 13:13:56 +0100 Subject: [PATCH 07/15] docs: update `docmain` post 1.32 release (#1567) * add docs check (#1516) * add docs check * add poetry.lock * circuit-display: update css file to v0.9 (#1521) * docs: update navbar config and theming submodule (#1523) * update navbar config * use latest quantinuum-sphinx * fix some local build warnings * bump quantinuum-sphinx (#1525) * release 1.31.1 (#1522) * update version and changelog * Update pytket/docs/changelog.rst * correct navbar links (#1527) * Updated flake.lock to bring symengine-0.12.0 into nix (#1423) * Update to pybind11 2.13.3 (#1531) * Fix symbol substitution for classical operations (#1538) * Update pybind11 and catch2 versions (#1539) * docs: Add pytket-azure to list of extensions (#1540) * feat: Refactor WASM module (#1503) * feat: Refactor WASM module - Add a new WasmModuleHandler class that takes raw wasm_module bytes on construction, but has the same interface as WasmFileHandler. - WasmFileHandler now inherits from WasmModuleHandler. - Split checking of the WASM module into a new function so it can be called after initialisation if required. - Adds new cached properties, bytecode, bytecode_base64 and module_uid which allow lazy loading of the module in encoded form, or computation of a unique identifier for the module. - Add deprecated properties _check_file, wasm_file_encoded and _wasmfileuid to maintain compatibility with existing versions of pytket-qir and pytket-quantinuum. - Remove some exceptions that are no longer possible. * Bugfix/incorrect multi line conditionals (#1510) * Add failing tests * Delay declaring registers * Handle conditional blocks * Raise error for conditional `RangePredicate` * Refactor `mark_as_written` * Refactor `self.range_preds` * Replacing conditions with predicates * Add changelog entry * Format code * Fix typing issue * Add testcase for multi-bit condition * Remove unnecessary 0 assignment * reposition comment in test * Fix order of `lower` and `upper` in constructing `RangePredicateOp` (#1549) * fix: Add copy implementations for Unit IDs (#1550) * Bump slackapi/slack-github-action from 1.26.0 to 1.27.0 (#1555) Bumps [slackapi/slack-github-action](https://github.com/slackapi/slack-github-action) from 1.26.0 to 1.27.0. - [Release notes](https://github.com/slackapi/slack-github-action/releases) - [Commits](https://github.com/slackapi/slack-github-action/compare/v1.26.0...v1.27.0) --- updated-dependencies: - dependency-name: slackapi/slack-github-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update version and changelog. (#1557) * Update to pybind11 2.13.5 (#1559) * Update libs to boost 1.86.0. (#1560) * Install pytket requirements before version consistency checks. (#1562) * Run valgrind on ubuntu-24.04 (#1563) * Allow constant ZZPhase fidelity in DecomposeTK2 (#1558) feat: Allow constant ZZPhase fidelity in DecomposeTK2 Currently it is not possible to serialize the Decompose TK2 pass as it can include an arbitrary function. This commit allows a constant float value in addition, which will allow serialization of the default compilation pass for pytket-quantinuum. --------- Co-authored-by: Alec Edgington * Use correct conan profile for valgrind build. (#1564) * Update to boost 1.86.0, tktokenswap 0.3.9 and tkwsm 0.3.9 (#1561) --------- Signed-off-by: dependabot[bot] Co-authored-by: cqc-melf <70640934+cqc-melf@users.noreply.github.com> Co-authored-by: Tiffany Duneau <37022011+DNA386@users.noreply.github.com> Co-authored-by: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Co-authored-by: John Children <32305209+johnchildren@users.noreply.github.com> Co-authored-by: yao-cqc <75305462+yao-cqc@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alec Edgington --- .github/workflows/build-without-conan.yml | 18 +- .github/workflows/build_and_test.yml | 21 + .github/workflows/pytket_benchmarking.yml | 2 +- .github/workflows/valgrind.yml | 8 +- build-without-conan.md | 18 +- flake.lock | 7 +- flake.nix | 2 +- libs/tkassert/test/conanfile.py | 2 +- libs/tklog/test/conanfile.py | 2 +- libs/tkrng/test/conanfile.py | 2 +- libs/tktokenswap/conanfile.py | 4 +- libs/tktokenswap/test/conanfile.py | 4 +- libs/tkwsm/conanfile.py | 4 +- libs/tkwsm/test/conanfile.py | 4 +- nix-support/symengine.patch | 2 +- pytket/binders/circuit/classical.cpp | 8 +- pytket/binders/passes.cpp | 8 +- pytket/binders/transform.cpp | 8 +- pytket/binders/unitid.cpp | 12 + pytket/conanfile.py | 8 +- pytket/docs/changelog.rst | 21 + pytket/docs/conf.py | 4 +- pytket/docs/poetry.lock | 2428 +++++++++++++++++ pytket/pytket/_tket/circuit.pyi | 4 +- pytket/pytket/_tket/passes.pyi | 2 +- pytket/pytket/_tket/transform.pyi | 2 +- pytket/pytket/_tket/unit_id.pyi | 12 + pytket/pytket/circuit/__init__.py | 8 +- .../circuit/display/static/head_imports.html | 2 +- pytket/pytket/qasm/qasm.py | 320 ++- pytket/pytket/wasm/wasm.py | 381 ++- pytket/tests/classical_test.py | 23 +- pytket/tests/passes_serialisation_test.py | 12 + pytket/tests/qasm_test.py | 136 + pytket/tests/transform_test.py | 10 + pytket/tests/unit_id/copy_test.py | 49 + recipes/pybind11/conanfile.py | 2 +- recipes/pybind11_json/all/conanfile.py | 2 +- schemas/compiler_pass_v1.json | 5 + tket/conanfile.py | 10 +- tket/include/tket/Ops/ClassicalOps.hpp | 46 +- .../tket/Transformations/Transform.hpp | 3 +- tket/include/tket/Utils/Expression.hpp | 9 + tket/src/Predicates/CompilerPass.cpp | 5 +- tket/src/Predicates/PassGenerators.cpp | 13 +- tket/src/Transformations/Decomposition.cpp | 17 +- tket/test/src/Circuit/test_Symbolic.cpp | 24 + .../Placement/test_NoiseAwarePlacement.cpp | 4 +- tket/test/src/test_json.cpp | 1 + 49 files changed, 3391 insertions(+), 308 deletions(-) create mode 100644 pytket/docs/poetry.lock create mode 100644 pytket/tests/unit_id/copy_test.py diff --git a/.github/workflows/build-without-conan.yml b/.github/workflows/build-without-conan.yml index cf12199431..c5aa9ae939 100644 --- a/.github/workflows/build-without-conan.yml +++ b/.github/workflows/build-without-conan.yml @@ -30,9 +30,9 @@ jobs: - name: Install boost run: | cd ${TMP_DIR} - wget -O boost_1_85_0.tar.gz https://sourceforge.net/projects/boost/files/boost/1.85.0/boost_1_85_0.tar.gz/download - tar xzvf boost_1_85_0.tar.gz - cd boost_1_85_0/ + wget -O boost_1_86_0.tar.gz https://sourceforge.net/projects/boost/files/boost/1.86.0/boost_1_86_0.tar.gz/download + tar xzvf boost_1_86_0.tar.gz + cd boost_1_86_0/ ./bootstrap.sh --prefix=${INSTALL_DIR} ./b2 ./b2 install @@ -74,9 +74,9 @@ jobs: - name: Install catch2 run: | cd ${TMP_DIR} - wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.6.0.tar.gz - tar xzvf v3.6.0.tar.gz - cd Catch2-3.6.0/ + wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.7.0.tar.gz + tar xzvf v3.7.0.tar.gz + cd Catch2-3.7.0/ mkdir build cd build cmake -GNinja -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} .. @@ -96,9 +96,9 @@ jobs: - name: Install pybind11 run: | cd ${TMP_DIR} - wget https://github.com/pybind/pybind11/archive/refs/tags/v2.13.1.tar.gz - tar xzvf v2.13.1.tar.gz - cd pybind11-2.13.1/ + wget https://github.com/pybind/pybind11/archive/refs/tags/v2.13.5.tar.gz + tar xzvf v2.13.5.tar.gz + cd pybind11-2.13.5/ mkdir build cd build cmake -GNinja -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DPYBIND11_TEST=OFF .. diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index cd02634a07..afaff3d71b 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -261,6 +261,10 @@ jobs: - name: Install tket if: needs.check_changes.outputs.tket_changed != 'true' run: conan install --requires tket/${{ needs.check_changes.outputs.tket_ver }}@tket/stable -o boost/*:header_only=True -o tklog/*:shared=True -o tket/*:shared=True + - name: Install pytket requirements + run: | + conan create recipes/pybind11 + conan create recipes/pybind11_json/all --version 0.2.14 - name: check that version is consistent if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' run: ./.github/workflows/check-tket-reqs ${{ needs.check_changes.outputs.tket_ver }} @@ -304,6 +308,15 @@ jobs: cd pytket/tests pip install -r requirements.txt pytest --ignore=simulator/ + - name: Test building docs + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + timeout-minutes: 20 + run: | + pip install poetry==1.6.1 + cd pytket/docs + poetry install + cd ../../ + poetry run -C pytket/docs ./.github/workflows/build-docs - name: Upload artifact if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' @@ -362,6 +375,10 @@ jobs: - name: Install tket if: needs.check_changes.outputs.tket_changed != 'true' run: conan install --requires tket/${{ needs.check_changes.outputs.tket_ver }}@tket/stable -o boost/*:header_only=True -o tklog/*:shared=True -o tket/*:shared=True + - name: Install pytket requirements + run: | + conan create recipes/pybind11 + conan create recipes/pybind11_json/all --version 0.2.14 - name: check that version is consistent if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' run: ./.github/workflows/check-tket-reqs ${{ needs.check_changes.outputs.tket_ver }} @@ -486,6 +503,10 @@ jobs: - name: Install tket if: needs.check_changes.outputs.tket_changed != 'true' run: conan install --requires tket/${{ needs.check_changes.outputs.tket_ver }}@tket/stable -o boost/*:header_only=True -o tklog/*:shared=True -o tket/*:shared=True + - name: Install pytket requirements + run: | + conan create recipes/pybind11 + conan create recipes/pybind11_json/all --version 0.2.14 - name: check that version is consistent if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' run: ./.github/workflows/check-tket-reqs ${{ needs.check_changes.outputs.tket_ver }} diff --git a/.github/workflows/pytket_benchmarking.yml b/.github/workflows/pytket_benchmarking.yml index 6d124b12cf..7d1d8a8c06 100644 --- a/.github/workflows/pytket_benchmarking.yml +++ b/.github/workflows/pytket_benchmarking.yml @@ -100,7 +100,7 @@ jobs: - name: Post to a Slack channel id: slack - uses: slackapi/slack-github-action@v1.26.0 + uses: slackapi/slack-github-action@v1.27.0 with: channel-id: 'G01CP0YFFA7' slack-message: '${{ env.RETURN_TEST }} Release tag: ${{ github.event.release.tag_name }}.' diff --git a/.github/workflows/valgrind.yml b/.github/workflows/valgrind.yml index 8cb69732d3..35c2621d90 100644 --- a/.github/workflows/valgrind.yml +++ b/.github/workflows/valgrind.yml @@ -28,7 +28,7 @@ jobs: - 'tket/**' - '.github/workflows/valgrind.yml' check: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: changes if: needs.changes.outputs.tket == 'true' steps: @@ -45,7 +45,7 @@ jobs: run: | conan profile detect DEFAULT_PROFILE_PATH=`conan profile path default` - PROFILE_PATH=./conan-profiles/ubuntu-22.04 + PROFILE_PATH=./conan-profiles/ubuntu-24.04 diff ${DEFAULT_PROFILE_PATH} ${PROFILE_PATH} || true cp ${PROFILE_PATH} ${DEFAULT_PROFILE_PATH} conan remote add tket-libs https://quantinuumsw.jfrog.io/artifactory/api/conan/tket1-libs --index 0 @@ -69,10 +69,10 @@ jobs: mkdir -p ~/texmf/tex/latex wget http://mirrors.ctan.org/graphics/pgf/contrib/quantikz/tikzlibraryquantikz.code.tex -P ~/texmf/tex/latex PKGPATH=`./rootpath tket.json tket` - cd ${PKGPATH}/bin && valgrind --error-exitcode=1 ./test-tket [long],~[long] + cd ${PKGPATH}/bin && valgrind --error-exitcode=1 --show-realloc-size-zero=no ./test-tket [long],~[long] - name: Run tests under valgrind (basic) if: github.event_name != 'schedule' run: | PKGPATH=`./rootpath tket.json tket` - cd ${PKGPATH}/bin && valgrind --error-exitcode=1 ./test-tket + cd ${PKGPATH}/bin && valgrind --error-exitcode=1 --show-realloc-size-zero=no ./test-tket diff --git a/build-without-conan.md b/build-without-conan.md index eab56f9e35..e1c8b2d12a 100644 --- a/build-without-conan.md +++ b/build-without-conan.md @@ -22,9 +22,9 @@ The versions should match the current requirements as specified in the relevant ``` cd ${TMP_DIR} -wget -O boost_1_85_0.tar.gz https://sourceforge.net/projects/boost/files/boost/1.85.0/boost_1_85_0.tar.gz/download -tar xzvf boost_1_85_0.tar.gz -cd boost_1_85_0/ +wget -O boost_1_86_0.tar.gz https://sourceforge.net/projects/boost/files/boost/1.86.0/boost_1_86_0.tar.gz/download +tar xzvf boost_1_86_0.tar.gz +cd boost_1_86_0/ ./bootstrap.sh --prefix=${INSTALL_DIR} ./b2 ./b2 install @@ -91,9 +91,9 @@ cmake --install . ``` cd ${TMP_DIR} -wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.6.0.tar.gz -tar xzvf v3.6.0.tar.gz -cd Catch2-3.6.0/ +wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.7.0.tar.gz +tar xzvf v3.7.0.tar.gz +cd Catch2-3.7.0/ mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} .. @@ -119,9 +119,9 @@ cmake --install . ``` cd ${TMP_DIR} -wget https://github.com/pybind/pybind11/archive/refs/tags/v2.13.1.tar.gz -tar xzvf v2.13.1.tar.gz -cd pybind11-2.13.1/ +wget https://github.com/pybind/pybind11/archive/refs/tags/v2.13.5.tar.gz +tar xzvf v2.13.5.tar.gz +cd pybind11-2.13.5/ mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DPYBIND11_TEST=OFF .. diff --git a/flake.lock b/flake.lock index efbf2a93c5..dacae6f678 100644 --- a/flake.lock +++ b/flake.lock @@ -20,16 +20,15 @@ }, "nixpkgs": { "locked": { - "lastModified": 1720386169, - "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=", + "lastModified": 1721812269, + "narHash": "sha256-qcI00YJBrLMmyPktlTS0UUvX/qGA7tVp73K6lJpRdy4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "194846768975b7ad2c4988bdb82572c00222c0d7", + "rev": "5c31ee6b23a06da40bf2dfc9017a60308a4adc53", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixos-24.05", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index e671d5e192..44dddc80e0 100644 --- a/flake.nix +++ b/flake.nix @@ -5,7 +5,7 @@ tket.cachix.org-1:ACdm5Zg19qPL0PpvUwTPPiIx8SEUy+D/uqa9vKJFwh0= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ''; - inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05"; + inputs.nixpkgs.url = "github:nixos/nixpkgs"; inputs.flake-utils.url = "github:numtide/flake-utils"; outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: diff --git a/libs/tkassert/test/conanfile.py b/libs/tkassert/test/conanfile.py index d92e5450b7..71ec809cbe 100644 --- a/libs/tkassert/test/conanfile.py +++ b/libs/tkassert/test/conanfile.py @@ -60,4 +60,4 @@ def package(self): def requirements(self): self.requires("tkassert/0.3.4") - self.requires("catch2/3.6.0") + self.requires("catch2/3.7.0") diff --git a/libs/tklog/test/conanfile.py b/libs/tklog/test/conanfile.py index 007d315926..b9f6454440 100644 --- a/libs/tklog/test/conanfile.py +++ b/libs/tklog/test/conanfile.py @@ -60,4 +60,4 @@ def package(self): def requirements(self): self.requires("tklog/0.3.3") - self.requires("catch2/3.6.0") + self.requires("catch2/3.7.0") diff --git a/libs/tkrng/test/conanfile.py b/libs/tkrng/test/conanfile.py index bcec0b0f04..0594ef7a37 100644 --- a/libs/tkrng/test/conanfile.py +++ b/libs/tkrng/test/conanfile.py @@ -60,4 +60,4 @@ def package(self): def requirements(self): self.requires("tkrng/0.3.3") - self.requires("catch2/3.6.0") + self.requires("catch2/3.7.0") diff --git a/libs/tktokenswap/conanfile.py b/libs/tktokenswap/conanfile.py index 3c3410586c..3fa0dad20a 100644 --- a/libs/tktokenswap/conanfile.py +++ b/libs/tktokenswap/conanfile.py @@ -19,7 +19,7 @@ class TktokenswapConan(ConanFile): name = "tktokenswap" - version = "0.3.8" + version = "0.3.9" package_type = "library" license = "Apache 2" url = "https://github.com/CQCL/tket" @@ -73,4 +73,4 @@ def requirements(self): self.requires("tklog/0.3.3@tket/stable") self.requires("tkassert/0.3.4@tket/stable", transitive_headers=True) self.requires("tkrng/0.3.3@tket/stable") - self.requires("boost/1.85.0", transitive_libs=False) + self.requires("boost/1.86.0", transitive_libs=False) diff --git a/libs/tktokenswap/test/conanfile.py b/libs/tktokenswap/test/conanfile.py index 180daf55bb..92371945c9 100644 --- a/libs/tktokenswap/test/conanfile.py +++ b/libs/tktokenswap/test/conanfile.py @@ -59,6 +59,6 @@ def package(self): cmake.install() def requirements(self): - self.requires("tktokenswap/0.3.8") + self.requires("tktokenswap/0.3.9") self.requires("tkrng/0.3.3@tket/stable") - self.requires("catch2/3.6.0") + self.requires("catch2/3.7.0") diff --git a/libs/tkwsm/conanfile.py b/libs/tkwsm/conanfile.py index ed1190d48b..78aa038f73 100644 --- a/libs/tkwsm/conanfile.py +++ b/libs/tkwsm/conanfile.py @@ -19,7 +19,7 @@ class TkwsmConan(ConanFile): name = "tkwsm" - version = "0.3.8" + version = "0.3.9" package_type = "library" license = "Apache 2" url = "https://github.com/CQCL/tket" @@ -72,4 +72,4 @@ def package_info(self): def requirements(self): self.requires("tkassert/0.3.4@tket/stable") self.requires("tkrng/0.3.3@tket/stable") - self.requires("boost/1.85.0", transitive_headers=True, transitive_libs=False) + self.requires("boost/1.86.0", transitive_headers=True, transitive_libs=False) diff --git a/libs/tkwsm/test/conanfile.py b/libs/tkwsm/test/conanfile.py index ab74d320ad..b68ecf7925 100644 --- a/libs/tkwsm/test/conanfile.py +++ b/libs/tkwsm/test/conanfile.py @@ -59,7 +59,7 @@ def package(self): cmake.install() def requirements(self): - self.requires("tkwsm/0.3.8") + self.requires("tkwsm/0.3.9") self.requires("tkassert/0.3.4@tket/stable") self.requires("tkrng/0.3.3@tket/stable") - self.requires("catch2/3.6.0") + self.requires("catch2/3.7.0") diff --git a/nix-support/symengine.patch b/nix-support/symengine.patch index b6e130f0f2..ac08dfd6ed 100644 --- a/nix-support/symengine.patch +++ b/nix-support/symengine.patch @@ -2,7 +2,7 @@ diff --git a/cmake/SymEngineConfig.cmake.in b/cmake/SymEngineConfig.cmake.in index dbfc80ba..d3a390b8 100644 --- a/cmake/SymEngineConfig.cmake.in +++ b/cmake/SymEngineConfig.cmake.in -@@ -107,11 +107,11 @@ endif() +@@ -109,11 +109,11 @@ endif() list(REMOVE_DUPLICATES SYMENGINE_INCLUDE_DIRS) diff --git a/pytket/binders/circuit/classical.cpp b/pytket/binders/circuit/classical.cpp index e8a0f9dd97..a1086886f1 100644 --- a/pytket/binders/circuit/classical.cpp +++ b/pytket/binders/circuit/classical.cpp @@ -123,12 +123,12 @@ void init_classical(py::module& m) { "A predicate defined by a range of values in binary encoding.") .def( py::init(), - "Construct from a bit width, an upper bound and a lower bound.", - py::arg("width"), py::arg("upper"), py::arg("lower")) + "Construct from a bit width, a lower bound and an upper bound.", + py::arg("width"), py::arg("lower"), py::arg("upper")) .def_property_readonly( - "upper", &RangePredicateOp::upper, "Inclusive upper bound.") + "lower", &RangePredicateOp::lower, "Inclusive lower bound.") .def_property_readonly( - "lower", &RangePredicateOp::lower, "Inclusive lower bound."); + "upper", &RangePredicateOp::upper, "Inclusive upper bound."); py::class_< ClassicalExpBox, std::shared_ptr>, Op>( diff --git a/pytket/binders/passes.cpp b/pytket/binders/passes.cpp index dfe27683da..3275a395c1 100644 --- a/pytket/binders/passes.cpp +++ b/pytket/binders/passes.cpp @@ -58,7 +58,7 @@ Transforms::TwoQbFidelities get_fidelities(const py::kwargs &kwargs) { } else if (kwargstr == "ZZMax_fidelity") { fid.ZZMax_fidelity = py::cast(kwarg.second); } else if (kwargstr == "ZZPhase_fidelity") { - fid.ZZPhase_fidelity = py::cast(kwarg.second); + fid.ZZPhase_fidelity = py::cast>(kwarg.second); } else { throw py::type_error( "got an unexpected keyword argument '" + kwargstr + "'"); @@ -405,9 +405,9 @@ PYBIND11_MODULE(passes, m) { "`ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities " "must be given by a single floating point fidelity. The `ZZPhase` " "fidelity is given as a lambda float -> float, mapping a ZZPhase " - "angle parameter to its fidelity. These parameters will be used " - "to return the optimal decomposition of each TK2 gate, taking " - "noise into consideration.\n\n" + "angle parameter to its fidelity, or by a single float. These parameters " + "will be used to return the optimal decomposition of each TK2 gate, " + "taking noise into consideration.\n\n" "If no fidelities are provided, the TK2 gates will be decomposed " "exactly using CX gates. For equal fidelities, ZZPhase will be prefered " "over ZZMax and CX if the decomposition results in fewer two-qubit " diff --git a/pytket/binders/transform.cpp b/pytket/binders/transform.cpp index e376762a37..8ef8027b78 100644 --- a/pytket/binders/transform.cpp +++ b/pytket/binders/transform.cpp @@ -48,7 +48,7 @@ Transforms::TwoQbFidelities get_fidelities(const py::kwargs &kwargs) { } else if (kwargstr == "ZZMax_fidelity") { fid.ZZMax_fidelity = py::cast(kwarg.second); } else if (kwargstr == "ZZPhase_fidelity") { - fid.ZZPhase_fidelity = py::cast(kwarg.second); + fid.ZZPhase_fidelity = py::cast>(kwarg.second); } else { throw py::type_error( "got an unexpected keyword argument '" + kwargstr + "'"); @@ -228,9 +228,9 @@ PYBIND11_MODULE(transform, m) { "`ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities " "must be given by a single floating point fidelity. The `ZZPhase` " "fidelity is given as a lambda float -> float, mapping a ZZPhase " - "angle parameter to its fidelity. These parameters will be used " - "to return the optimal decomposition of each TK2 gate, taking " - "noise into consideration.\n\n" + "angle parameter to its fidelity, or by a single float. These " + "parameters will be used to return the optimal decomposition of each " + "TK2 gate, taking noise into consideration.\n\n" "Using the `allow_swaps=True` (default) option, qubits will be " "swapped when convenient to reduce the two-qubit gate count of the " "decomposed TK2.\n\n" diff --git a/pytket/binders/unitid.cpp b/pytket/binders/unitid.cpp index 3d9d344807..2f3cecc8a6 100644 --- a/pytket/binders/unitid.cpp +++ b/pytket/binders/unitid.cpp @@ -119,6 +119,10 @@ PYBIND11_MODULE(unit_id, m) { "``UnitType.bit``"); py::class_(m, "Qubit", "A handle to a qubit") + .def("__copy__", [](const Qubit &id) { return Qubit(id); }) + .def( + "__deepcopy__", + [](const Qubit &id, const py::dict &) { return Qubit(id); }) .def( py::init(), "Constructs an id for some index in the default qubit " @@ -172,6 +176,10 @@ PYBIND11_MODULE(unit_id, m) { "list representation of the Qubit."); py::class_(m, "Bit", "A handle to a bit") + .def("__copy__", [](const Bit &id) { return Bit(id); }) + .def( + "__deepcopy__", + [](const Bit &id, const py::dict &) { return Bit(id); }) .def( py::init(), "Constructs an id for some index in the default classical " @@ -226,6 +234,10 @@ PYBIND11_MODULE(unit_id, m) { "list representation of the Bit."); py::class_(m, "Node", "A handle to a device node") + .def("__copy__", [](const Node &id) { return Node(id); }) + .def( + "__deepcopy__", + [](const Node &id, const py::dict &) { return Node(id); }) .def( py::init(), "Constructs an id for some index in the default physical " diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 6bc2dc7af3..a5ca25a70a 100644 --- a/pytket/conanfile.py +++ b/pytket/conanfile.py @@ -32,14 +32,14 @@ def package(self): cmake.install() def requirements(self): - self.requires("tket/1.3.17@tket/stable") + self.requires("tket/1.3.23@tket/stable") self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") self.requires("tkassert/0.3.4@tket/stable") - self.requires("tkwsm/0.3.8@tket/stable") - self.requires("tktokenswap/0.3.8@tket/stable") + self.requires("tkwsm/0.3.9@tket/stable") + self.requires("tktokenswap/0.3.9@tket/stable") self.requires("symengine/0.12.0") self.requires("gmp/6.3.0") - self.requires("pybind11/2.13.1") + self.requires("pybind11/2.13.5") self.requires("nlohmann_json/3.11.3") self.requires("pybind11_json/0.2.14") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index cdc6cc6994..d8ff307da7 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -1,6 +1,27 @@ Changelog ========= +Unreleased +---------- + +Features: + +* DecomposeTK2 pass and transform can now accept a float for ZZPhase_fidelity. +* DecomposeTK2 pass now has a json representation when it contains no functions. + +1.32.0 (September 2024) +----------------------- + +* Fix symbol substitution for classical operations. +* Fix incorrect QASM conversion of conditional multi-line ops. +* Fix incorrect order of `lower` and `upper` properties of `RangePredicateOp`. + + +1.31.1 (August 2024) +-------------------- + +* Fix issue with css styles in pytket-circuit-renderer 0.9. + 1.31.0 (August 2024) -------------------- diff --git a/pytket/docs/conf.py b/pytket/docs/conf.py index 56111227a1..21b3908018 100644 --- a/pytket/docs/conf.py +++ b/pytket/docs/conf.py @@ -38,9 +38,9 @@ author = "Quantinuum" # The short X.Y version -version = "1.31" +version = "1.32" # The full version, including alpha/beta/rc tags -release = "1.31.0" +release = "1.32.0" # -- General configuration --------------------------------------------------- diff --git a/pytket/docs/poetry.lock b/pytket/docs/poetry.lock new file mode 100644 index 0000000000..5f561d0a13 --- /dev/null +++ b/pytket/docs/poetry.lock @@ -0,0 +1,2428 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "alabaster" +version = "0.7.16" +description = "A light, configurable Sphinx theme" +optional = false +python-versions = ">=3.9" +files = [ + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, +] + +[[package]] +name = "apeye" +version = "1.4.1" +description = "Handy tools for working with URLs and APIs." +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "apeye-1.4.1-py3-none-any.whl", hash = "sha256:44e58a9104ec189bf42e76b3a7fe91e2b2879d96d48e9a77e5e32ff699c9204e"}, + {file = "apeye-1.4.1.tar.gz", hash = "sha256:14ea542fad689e3bfdbda2189a354a4908e90aee4bf84c15ab75d68453d76a36"}, +] + +[package.dependencies] +apeye-core = ">=1.0.0b2" +domdf-python-tools = ">=2.6.0" +platformdirs = ">=2.3.0" +requests = ">=2.24.0" + +[package.extras] +all = ["cachecontrol[filecache] (>=0.12.6)", "lockfile (>=0.12.2)"] +limiter = ["cachecontrol[filecache] (>=0.12.6)", "lockfile (>=0.12.2)"] + +[[package]] +name = "apeye-core" +version = "1.1.5" +description = "Core (offline) functionality for the apeye library." +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "apeye_core-1.1.5-py3-none-any.whl", hash = "sha256:dc27a93f8c9e246b3b238c5ea51edf6115ab2618ef029b9f2d9a190ec8228fbf"}, + {file = "apeye_core-1.1.5.tar.gz", hash = "sha256:5de72ed3d00cc9b20fea55e54b7ab8f5ef8500eb33a5368bc162a5585e238a55"}, +] + +[package.dependencies] +domdf-python-tools = ">=2.6.0" +idna = ">=2.5" + +[[package]] +name = "appnope" +version = "0.1.4" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = ">=3.6" +files = [ + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, +] + +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] + +[[package]] +name = "autodocsumm" +version = "0.2.12" +description = "Extended sphinx autodoc including automatic autosummaries" +optional = false +python-versions = ">=3.7" +files = [ + {file = "autodocsumm-0.2.12-py3-none-any.whl", hash = "sha256:b842b53c686c07a4f174721ca4e729b027367703dbf42e2508863a3c6d6c049c"}, + {file = "autodocsumm-0.2.12.tar.gz", hash = "sha256:848fe8c38df433c6635489499b969cb47cc389ed3d7b6e75c8ccbc94d4b3bf9e"}, +] + +[package.dependencies] +Sphinx = ">=2.2,<8.0" + +[[package]] +name = "babel" +version = "2.15.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "bleach" +version = "6.1.0" +description = "An easy safelist-based HTML-sanitizing tool." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bleach-6.1.0-py3-none-any.whl", hash = "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6"}, + {file = "bleach-6.1.0.tar.gz", hash = "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe"}, +] + +[package.dependencies] +six = ">=1.9.0" +webencodings = "*" + +[package.extras] +css = ["tinycss2 (>=1.1.0,<1.3)"] + +[[package]] +name = "cachecontrol" +version = "0.14.0" +description = "httplib2 caching for requests" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachecontrol-0.14.0-py3-none-any.whl", hash = "sha256:f5bf3f0620c38db2e5122c0726bdebb0d16869de966ea6a2befe92470b740ea0"}, + {file = "cachecontrol-0.14.0.tar.gz", hash = "sha256:7db1195b41c81f8274a7bbd97c956f44e8348265a1bc7641c37dfebc39f0c938"}, +] + +[package.dependencies] +filelock = {version = ">=3.8.0", optional = true, markers = "extra == \"filecache\""} +msgpack = ">=0.5.2,<2.0.0" +requests = ">=2.16.0" + +[package.extras] +dev = ["CacheControl[filecache,redis]", "black", "build", "cherrypy", "furo", "mypy", "pytest", "pytest-cov", "sphinx", "sphinx-copybutton", "tox", "types-redis", "types-requests"] +filecache = ["filelock (>=3.8.0)"] +redis = ["redis (>=2.10.5)"] + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "comm" +version = "0.2.2" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.8" +files = [ + {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, + {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, +] + +[package.dependencies] +traitlets = ">=4" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "cssutils" +version = "2.11.1" +description = "A CSS Cascading Style Sheets library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cssutils-2.11.1-py3-none-any.whl", hash = "sha256:a67bfdfdff4f3867fab43698ec4897c1a828eca5973f4073321b3bccaf1199b1"}, + {file = "cssutils-2.11.1.tar.gz", hash = "sha256:0563a76513b6af6eebbe788c3bf3d01c920e46b3f90c8416738c5cfc773ff8e2"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["cssselect", "importlib-resources", "jaraco.test (>=5.1)", "lxml", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "debugpy" +version = "1.8.2" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "debugpy-1.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:7ee2e1afbf44b138c005e4380097d92532e1001580853a7cb40ed84e0ef1c3d2"}, + {file = "debugpy-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f8c3f7c53130a070f0fc845a0f2cee8ed88d220d6b04595897b66605df1edd6"}, + {file = "debugpy-1.8.2-cp310-cp310-win32.whl", hash = "sha256:f179af1e1bd4c88b0b9f0fa153569b24f6b6f3de33f94703336363ae62f4bf47"}, + {file = "debugpy-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:0600faef1d0b8d0e85c816b8bb0cb90ed94fc611f308d5fde28cb8b3d2ff0fe3"}, + {file = "debugpy-1.8.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8a13417ccd5978a642e91fb79b871baded925d4fadd4dfafec1928196292aa0a"}, + {file = "debugpy-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acdf39855f65c48ac9667b2801234fc64d46778021efac2de7e50907ab90c634"}, + {file = "debugpy-1.8.2-cp311-cp311-win32.whl", hash = "sha256:2cbd4d9a2fc5e7f583ff9bf11f3b7d78dfda8401e8bb6856ad1ed190be4281ad"}, + {file = "debugpy-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:d3408fddd76414034c02880e891ea434e9a9cf3a69842098ef92f6e809d09afa"}, + {file = "debugpy-1.8.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:5d3ccd39e4021f2eb86b8d748a96c766058b39443c1f18b2dc52c10ac2757835"}, + {file = "debugpy-1.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62658aefe289598680193ff655ff3940e2a601765259b123dc7f89c0239b8cd3"}, + {file = "debugpy-1.8.2-cp312-cp312-win32.whl", hash = "sha256:bd11fe35d6fd3431f1546d94121322c0ac572e1bfb1f6be0e9b8655fb4ea941e"}, + {file = "debugpy-1.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:15bc2f4b0f5e99bf86c162c91a74c0631dbd9cef3c6a1d1329c946586255e859"}, + {file = "debugpy-1.8.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:5a019d4574afedc6ead1daa22736c530712465c0c4cd44f820d803d937531b2d"}, + {file = "debugpy-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40f062d6877d2e45b112c0bbade9a17aac507445fd638922b1a5434df34aed02"}, + {file = "debugpy-1.8.2-cp38-cp38-win32.whl", hash = "sha256:c78ba1680f1015c0ca7115671fe347b28b446081dada3fedf54138f44e4ba031"}, + {file = "debugpy-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cf327316ae0c0e7dd81eb92d24ba8b5e88bb4d1b585b5c0d32929274a66a5210"}, + {file = "debugpy-1.8.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:1523bc551e28e15147815d1397afc150ac99dbd3a8e64641d53425dba57b0ff9"}, + {file = "debugpy-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e24ccb0cd6f8bfaec68d577cb49e9c680621c336f347479b3fce060ba7c09ec1"}, + {file = "debugpy-1.8.2-cp39-cp39-win32.whl", hash = "sha256:7f8d57a98c5a486c5c7824bc0b9f2f11189d08d73635c326abef268f83950326"}, + {file = "debugpy-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:16c8dcab02617b75697a0a925a62943e26a0330da076e2a10437edd9f0bf3755"}, + {file = "debugpy-1.8.2-py2.py3-none-any.whl", hash = "sha256:16e16df3a98a35c63c3ab1e4d19be4cbc7fdda92d9ddc059294f18910928e0ca"}, + {file = "debugpy-1.8.2.zip", hash = "sha256:95378ed08ed2089221896b9b3a8d021e642c24edc8fef20e5d4342ca8be65c00"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + +[[package]] +name = "dict2css" +version = "0.3.0.post1" +description = "A μ-library for constructing cascading style sheets from Python dictionaries." +optional = false +python-versions = ">=3.6" +files = [ + {file = "dict2css-0.3.0.post1-py3-none-any.whl", hash = "sha256:f006a6b774c3e31869015122ae82c491fd25e7de4a75607a62aa3e798f837e0d"}, + {file = "dict2css-0.3.0.post1.tar.gz", hash = "sha256:89c544c21c4ca7472c3fffb9d37d3d926f606329afdb751dc1de67a411b70719"}, +] + +[package.dependencies] +cssutils = ">=2.2.0" +domdf-python-tools = ">=2.2.0" + +[[package]] +name = "docutils" +version = "0.21.2" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.9" +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + +[[package]] +name = "domdf-python-tools" +version = "3.9.0" +description = "Helpful functions for Python 🐍 🛠️" +optional = false +python-versions = ">=3.6" +files = [ + {file = "domdf_python_tools-3.9.0-py3-none-any.whl", hash = "sha256:4e1ef365cbc24627d6d1e90cf7d46d8ab8df967e1237f4a26885f6986c78872e"}, + {file = "domdf_python_tools-3.9.0.tar.gz", hash = "sha256:1f8a96971178333a55e083e35610d7688cd7620ad2b99790164e1fc1a3614c18"}, +] + +[package.dependencies] +natsort = ">=7.0.1" +typing-extensions = ">=3.7.4.1" + +[package.extras] +all = ["pytz (>=2019.1)"] +dates = ["pytz (>=2019.1)"] + +[[package]] +name = "enum-tools" +version = "0.12.0" +description = "Tools to expand Python's enum module." +optional = false +python-versions = ">=3.6" +files = [ + {file = "enum_tools-0.12.0-py3-none-any.whl", hash = "sha256:d69b019f193c7b850b17d9ce18440db7ed62381571409af80ccc08c5218b340a"}, + {file = "enum_tools-0.12.0.tar.gz", hash = "sha256:13ceb9376a4c5f574a1e7c5f9c8eb7f3d3fbfbb361cc18a738df1a58dfefd460"}, +] + +[package.dependencies] +pygments = ">=2.6.1" +sphinx = {version = ">=3.4.0", optional = true, markers = "extra == \"sphinx\""} +sphinx-jinja2-compat = {version = ">=0.1.1", optional = true, markers = "extra == \"sphinx\""} +sphinx-toolbox = {version = ">=2.16.0", optional = true, markers = "extra == \"sphinx\""} +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["sphinx (>=3.4.0)", "sphinx-jinja2-compat (>=0.1.1)", "sphinx-toolbox (>=2.16.0)"] +sphinx = ["sphinx (>=3.4.0)", "sphinx-jinja2-compat (>=0.1.1)", "sphinx-toolbox (>=2.16.0)"] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "executing" +version = "2.0.1" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.5" +files = [ + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + +[[package]] +name = "fastjsonschema" +version = "2.20.0" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +files = [ + {file = "fastjsonschema-2.20.0-py3-none-any.whl", hash = "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a"}, + {file = "fastjsonschema-2.20.0.tar.gz", hash = "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "filelock" +version = "3.15.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, + {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] +typing = ["typing-extensions (>=4.8)"] + +[[package]] +name = "furo" +version = "2024.7.18" +description = "A clean customisable Sphinx documentation theme." +optional = false +python-versions = ">=3.8" +files = [ + {file = "furo-2024.7.18-py3-none-any.whl", hash = "sha256:b192c7c1f59805494c8ed606d9375fdac6e6ba8178e747e72bc116745fb7e13f"}, + {file = "furo-2024.7.18.tar.gz", hash = "sha256:37b08c5fccc95d46d8712c8be97acd46043963895edde05b0f4f135d58325c83"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +pygments = ">=2.7" +sphinx = ">=6.0,<8.0" +sphinx-basic-ng = ">=1.0.0.beta2" + +[[package]] +name = "graphviz" +version = "0.20.3" +description = "Simple Python interface for Graphviz" +optional = false +python-versions = ">=3.8" +files = [ + {file = "graphviz-0.20.3-py3-none-any.whl", hash = "sha256:81f848f2904515d8cd359cc611faba817598d2feaac4027b266aa3eda7b3dde5"}, + {file = "graphviz-0.20.3.zip", hash = "sha256:09d6bc81e6a9fa392e7ba52135a9d49f1ed62526f96499325930e87ca1b5925d"}, +] + +[package.extras] +dev = ["flake8", "pep8-naming", "tox (>=3)", "twine", "wheel"] +docs = ["sphinx (>=5,<7)", "sphinx-autodoc-typehints", "sphinx-rtd-theme"] +test = ["coverage", "pytest (>=7,<8.1)", "pytest-cov", "pytest-mock (>=3)"] + +[[package]] +name = "html5lib" +version = "1.1" +description = "HTML parser based on the WHATWG HTML specification" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[package.extras] +all = ["chardet (>=2.2)", "genshi", "lxml"] +chardet = ["chardet (>=2.2)"] +genshi = ["genshi"] +lxml = ["lxml"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "ipykernel" +version = "6.29.5" +description = "IPython Kernel for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, + {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = "*" +packaging = "*" +psutil = "*" +pyzmq = ">=24" +tornado = ">=6.1" +traitlets = ">=5.4.0" + +[package.extras] +cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "8.26.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.10" +files = [ + {file = "ipython-8.26.0-py3-none-any.whl", hash = "sha256:e6b347c27bdf9c32ee9d31ae85defc525755a1869f14057e900675b9e8d6e6ff"}, + {file = "ipython-8.26.0.tar.gz", hash = "sha256:1cec0fbba8404af13facebe83d04436a7434c7400e59f47acf467c64abd0956c"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt-toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5.13.0" +typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} + +[package.extras] +all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] +kernel = ["ipykernel"] +matplotlib = ["matplotlib"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] + +[[package]] +name = "ipywidgets" +version = "8.1.3" +description = "Jupyter interactive widgets" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ipywidgets-8.1.3-py3-none-any.whl", hash = "sha256:efafd18f7a142248f7cb0ba890a68b96abd4d6e88ddbda483c9130d12667eaf2"}, + {file = "ipywidgets-8.1.3.tar.gz", hash = "sha256:f5f9eeaae082b1823ce9eac2575272952f40d748893972956dc09700a6392d9c"}, +] + +[package.dependencies] +comm = ">=0.1.3" +ipython = ">=6.1.0" +jupyterlab-widgets = ">=3.0.11,<3.1.0" +traitlets = ">=4.3.1" +widgetsnbextension = ">=4.0.11,<4.1.0" + +[package.extras] +test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] + +[[package]] +name = "jedi" +version = "0.19.1" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jsonschema" +version = "4.23.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + +[[package]] +name = "jupyter-client" +version = "8.6.2" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_client-8.6.2-py3-none-any.whl", hash = "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f"}, + {file = "jupyter_client-8.6.2.tar.gz", hash = "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df"}, +] + +[package.dependencies] +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, + {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "jupyter-sphinx" +version = "0.5.3" +description = "Jupyter Sphinx Extensions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_sphinx-0.5.3-py3-none-any.whl", hash = "sha256:a67b3208d4da5b3508dbb8260d3b359ae476c36c6c642747b78a2520e5be0b05"}, + {file = "jupyter_sphinx-0.5.3.tar.gz", hash = "sha256:2e23699a3a1cf5db31b10981da5aa32606ee730f6b73a844d1e76d800756af56"}, +] + +[package.dependencies] +ipykernel = ">=4.5.1" +ipython = "*" +ipywidgets = ">=7.0.0" +nbconvert = ">=5.5" +nbformat = "*" +sphinx = ">=7" + +[package.extras] +doc = ["matplotlib"] +test = ["bash-kernel", "pytest"] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +description = "Pygments theme using JupyterLab CSS variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"}, + {file = "jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d"}, +] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.11" +description = "Jupyter interactive widgets for JupyterLab" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_widgets-3.0.11-py3-none-any.whl", hash = "sha256:78287fd86d20744ace330a61625024cf5521e1c012a352ddc0a3cdc2348becd0"}, + {file = "jupyterlab_widgets-3.0.11.tar.gz", hash = "sha256:dd5ac679593c969af29c9bed054c24f26842baa51352114736756bc035deee27"}, +] + +[[package]] +name = "lark" +version = "1.1.9" +description = "a modern parsing library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "lark-1.1.9-py3-none-any.whl", hash = "sha256:a0dd3a87289f8ccbb325901e4222e723e7d745dbfc1803eaf5f3d2ace19cf2db"}, + {file = "lark-1.1.9.tar.gz", hash = "sha256:15fa5236490824c2c4aba0e22d2d6d823575dcaf4cdd1848e34b6ad836240fba"}, +] + +[package.extras] +atomic-cache = ["atomicwrites"] +interegular = ["interegular (>=0.3.1,<0.4.0)"] +nearley = ["js2py"] +regex = ["regex"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mistune" +version = "3.0.2" +description = "A sane and fast Markdown parser with useful plugins and renderers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"}, + {file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"}, +] + +[[package]] +name = "more-itertools" +version = "10.3.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"}, + {file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"}, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +description = "Python library for arbitrary-precision floating-point arithmetic" +optional = false +python-versions = "*" +files = [ + {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, + {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, +] + +[package.extras] +develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] +docs = ["sphinx"] +gmpy = ["gmpy2 (>=2.1.0a4)"] +tests = ["pytest (>=4.6)"] + +[[package]] +name = "msgpack" +version = "1.0.8" +description = "MessagePack serializer" +optional = false +python-versions = ">=3.8" +files = [ + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, + {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, + {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, + {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, + {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, + {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, + {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, + {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, + {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, + {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, + {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, +] + +[[package]] +name = "natsort" +version = "8.4.0" +description = "Simple yet flexible natural sorting in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c"}, + {file = "natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581"}, +] + +[package.extras] +fast = ["fastnumbers (>=2.0.0)"] +icu = ["PyICU (>=1.0.0)"] + +[[package]] +name = "nbclient" +version = "0.10.0" +description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "nbclient-0.10.0-py3-none-any.whl", hash = "sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f"}, + {file = "nbclient-0.10.0.tar.gz", hash = "sha256:4b3f1b7dba531e498449c4db4f53da339c91d449dc11e9af3a43b4eb5c5abb09"}, +] + +[package.dependencies] +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +nbformat = ">=5.1" +traitlets = ">=5.4" + +[package.extras] +dev = ["pre-commit"] +docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling"] +test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0,<8)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] + +[[package]] +name = "nbconvert" +version = "7.16.4" +description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." +optional = false +python-versions = ">=3.8" +files = [ + {file = "nbconvert-7.16.4-py3-none-any.whl", hash = "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3"}, + {file = "nbconvert-7.16.4.tar.gz", hash = "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +bleach = "!=5.0.0" +defusedxml = "*" +jinja2 = ">=3.0" +jupyter-core = ">=4.7" +jupyterlab-pygments = "*" +markupsafe = ">=2.0" +mistune = ">=2.0.3,<4" +nbclient = ">=0.5.0" +nbformat = ">=5.7" +packaging = "*" +pandocfilters = ">=1.4.1" +pygments = ">=2.4.1" +tinycss2 = "*" +traitlets = ">=5.1" + +[package.extras] +all = ["flaky", "ipykernel", "ipython", "ipywidgets (>=7.5)", "myst-parser", "nbsphinx (>=0.2.12)", "playwright", "pydata-sphinx-theme", "pyqtwebengine (>=5.15)", "pytest (>=7)", "sphinx (==5.0.2)", "sphinxcontrib-spelling", "tornado (>=6.1)"] +docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sphinx-theme", "sphinx (==5.0.2)", "sphinxcontrib-spelling"] +qtpdf = ["pyqtwebengine (>=5.15)"] +qtpng = ["pyqtwebengine (>=5.15)"] +serve = ["tornado (>=6.1)"] +test = ["flaky", "ipykernel", "ipywidgets (>=7.5)", "pytest (>=7)"] +webpdf = ["playwright"] + +[[package]] +name = "nbformat" +version = "5.10.4" +description = "The Jupyter Notebook format" +optional = false +python-versions = ">=3.8" +files = [ + {file = "nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b"}, + {file = "nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a"}, +] + +[package.dependencies] +fastjsonschema = ">=2.15" +jsonschema = ">=2.6" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +traitlets = ">=5.1" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["pep440", "pre-commit", "pytest", "testpath"] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + +[[package]] +name = "networkx" +version = "3.3" +description = "Python package for creating and manipulating graphs and networks" +optional = false +python-versions = ">=3.10" +files = [ + {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"}, + {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}, +] + +[package.extras] +default = ["matplotlib (>=3.6)", "numpy (>=1.23)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["myst-nb (>=1.0)", "numpydoc (>=1.7)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=2.0)", "pygraphviz (>=1.12)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] + +[[package]] +name = "numpy" +version = "2.0.1" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02"}, + {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101"}, + {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9"}, + {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015"}, + {file = "numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87"}, + {file = "numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82"}, + {file = "numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1"}, + {file = "numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343"}, + {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b"}, + {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe"}, + {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67"}, + {file = "numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7"}, + {file = "numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55"}, + {file = "numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4"}, + {file = "numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"}, + {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"}, + {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"}, + {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"}, + {file = "numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"}, + {file = "numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"}, + {file = "numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"}, + {file = "numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06"}, + {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c"}, + {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59"}, + {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26"}, + {file = "numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018"}, + {file = "numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c"}, + {file = "numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"}, + {file = "numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990"}, + {file = "numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f"}, + {file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +description = "Utilities for writing pandoc filters in python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"}, + {file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e"}, +] + +[[package]] +name = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.47" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, + {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "6.0.0" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, + {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, + {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, + {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, + {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, + {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, + {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, + {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, + {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, + {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytket" +version = "1.31.0" +description = "Quantum computing toolkit and interface to the TKET compiler" +optional = false +python-versions = ">=3.10" +files = [ + {file = "pytket-1.31.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6a1981f2e8fcfa4bfa2bf7c2732f7062646415d2846a717d9542f44fb2c506a7"}, + {file = "pytket-1.31.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:625fa01d930cafb99538fe9d6134039d25feb27bba528f32a78e1679d4103d0f"}, + {file = "pytket-1.31.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33762b67a39fe0052afa39a21e6b0f9c045d7c5639a7571ece4cf1bcb0b68e3f"}, + {file = "pytket-1.31.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9878468c7fda0eb8ca3f0b9c349f811a1e7a7c896463892bfea9b66f88c99447"}, + {file = "pytket-1.31.0-cp310-cp310-win_amd64.whl", hash = "sha256:8cc42819d6ca0e4c2587235fc78ec6ae2b75610190f0a0ab369911043e6a7dc0"}, + {file = "pytket-1.31.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:be5fab98fcc14860ee4effe0a7657b39c2487a29c048e0ab228b7be3ec80e049"}, + {file = "pytket-1.31.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:258ee145704e040d352c164e11d794bf612d26cc37c1f585a5453397a86f7113"}, + {file = "pytket-1.31.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a2f7ff90a2d73447d1e45c2f56ce08116634a4c2986312fc88a7903a328b4b5"}, + {file = "pytket-1.31.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5069fd53997ae93826df24ff7e2b5309d4d0ae645b6c10d3ac62725688b2556f"}, + {file = "pytket-1.31.0-cp311-cp311-win_amd64.whl", hash = "sha256:e4e361269f611869495ac34aaa68a07994f76bd35d21962629d2aa54a62d5726"}, + {file = "pytket-1.31.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:848350011e8f04f59ec43deb79b1fd1ff00382f71fd208adfa972c0b74efa09d"}, + {file = "pytket-1.31.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:88957b28fec827a55eceac7d4c70e29ddf89531cb62c325585cab7600a4c55b9"}, + {file = "pytket-1.31.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f674664465c04336f7d85b6f0aafdd28f5437acd647c9083df1433a90d10a98d"}, + {file = "pytket-1.31.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fa61c7a629402fb260a71c6425466ad205ef148463959f1b29717fd66a79681"}, + {file = "pytket-1.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:c414598ea4a66a8075943cdee067911af597ca9dc0a22e9ffa4596c8e995abe2"}, +] + +[package.dependencies] +graphviz = ">=0.20.3" +jinja2 = ">=3.1.4" +lark = ">=1.1.9" +networkx = ">=2.8.8" +numpy = ">=1.26.4" +qwasm = ">=1.0.1" +scipy = ">=1.13.1" +sympy = ">=1.12.1" +typing-extensions = ">=4.12.2" + +[package.extras] +zx = ["autoray (>=0.6.12)", "numba (>=0.60.0)", "quimb (>=1.8.2)"] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pyzmq" +version = "26.0.3" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625"}, + {file = "pyzmq-26.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee"}, + {file = "pyzmq-26.0.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc"}, + {file = "pyzmq-26.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8"}, + {file = "pyzmq-26.0.3-cp310-cp310-win32.whl", hash = "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537"}, + {file = "pyzmq-26.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47"}, + {file = "pyzmq-26.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7"}, + {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32"}, + {file = "pyzmq-26.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527"}, + {file = "pyzmq-26.0.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd"}, + {file = "pyzmq-26.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83"}, + {file = "pyzmq-26.0.3-cp311-cp311-win32.whl", hash = "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3"}, + {file = "pyzmq-26.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500"}, + {file = "pyzmq-26.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94"}, + {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753"}, + {file = "pyzmq-26.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02"}, + {file = "pyzmq-26.0.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2"}, + {file = "pyzmq-26.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798"}, + {file = "pyzmq-26.0.3-cp312-cp312-win32.whl", hash = "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0"}, + {file = "pyzmq-26.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf"}, + {file = "pyzmq-26.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b"}, + {file = "pyzmq-26.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa"}, + {file = "pyzmq-26.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a"}, + {file = "pyzmq-26.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5"}, + {file = "pyzmq-26.0.3-cp37-cp37m-win32.whl", hash = "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf"}, + {file = "pyzmq-26.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a"}, + {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18"}, + {file = "pyzmq-26.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad"}, + {file = "pyzmq-26.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c"}, + {file = "pyzmq-26.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97"}, + {file = "pyzmq-26.0.3-cp38-cp38-win32.whl", hash = "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc"}, + {file = "pyzmq-26.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972"}, + {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606"}, + {file = "pyzmq-26.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8"}, + {file = "pyzmq-26.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab"}, + {file = "pyzmq-26.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920"}, + {file = "pyzmq-26.0.3-cp39-cp39-win32.whl", hash = "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879"}, + {file = "pyzmq-26.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2"}, + {file = "pyzmq-26.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223"}, + {file = "pyzmq-26.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709"}, + {file = "pyzmq-26.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480"}, + {file = "pyzmq-26.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d"}, + {file = "pyzmq-26.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad"}, + {file = "pyzmq-26.0.3.tar.gz", hash = "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "qwasm" +version = "1.0.1" +description = "WebAssembly decoder & disassembler" +optional = false +python-versions = "*" +files = [ + {file = "qwasm-1.0.1-py3-none-any.whl", hash = "sha256:c4c82a3f962d29314634868e06375f0cb4676c3d5266fbe137f6cd67321b0ef1"}, + {file = "qwasm-1.0.1.tar.gz", hash = "sha256:01f5dfe27159b7fdd9d02cd299833225d528fa383d1278268e5e1526357950fb"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "referencing" +version = "0.35.1" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, + {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rpds-py" +version = "0.19.1" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.19.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:aaf71f95b21f9dc708123335df22e5a2fef6307e3e6f9ed773b2e0938cc4d491"}, + {file = "rpds_py-0.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca0dda0c5715efe2ab35bb83f813f681ebcd2840d8b1b92bfc6fe3ab382fae4a"}, + {file = "rpds_py-0.19.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81db2e7282cc0487f500d4db203edc57da81acde9e35f061d69ed983228ffe3b"}, + {file = "rpds_py-0.19.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1a8dfa125b60ec00c7c9baef945bb04abf8ac772d8ebefd79dae2a5f316d7850"}, + {file = "rpds_py-0.19.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:271accf41b02687cef26367c775ab220372ee0f4925591c6796e7c148c50cab5"}, + {file = "rpds_py-0.19.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9bc4161bd3b970cd6a6fcda70583ad4afd10f2750609fb1f3ca9505050d4ef3"}, + {file = "rpds_py-0.19.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0cf2a0dbb5987da4bd92a7ca727eadb225581dd9681365beba9accbe5308f7d"}, + {file = "rpds_py-0.19.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b5e28e56143750808c1c79c70a16519e9bc0a68b623197b96292b21b62d6055c"}, + {file = "rpds_py-0.19.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c7af6f7b80f687b33a4cdb0a785a5d4de1fb027a44c9a049d8eb67d5bfe8a687"}, + {file = "rpds_py-0.19.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e429fc517a1c5e2a70d576077231538a98d59a45dfc552d1ac45a132844e6dfb"}, + {file = "rpds_py-0.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d2dbd8f4990d4788cb122f63bf000357533f34860d269c1a8e90ae362090ff3a"}, + {file = "rpds_py-0.19.1-cp310-none-win32.whl", hash = "sha256:e0f9d268b19e8f61bf42a1da48276bcd05f7ab5560311f541d22557f8227b866"}, + {file = "rpds_py-0.19.1-cp310-none-win_amd64.whl", hash = "sha256:df7c841813f6265e636fe548a49664c77af31ddfa0085515326342a751a6ba51"}, + {file = "rpds_py-0.19.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:902cf4739458852fe917104365ec0efbea7d29a15e4276c96a8d33e6ed8ec137"}, + {file = "rpds_py-0.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3d73022990ab0c8b172cce57c69fd9a89c24fd473a5e79cbce92df87e3d9c48"}, + {file = "rpds_py-0.19.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3837c63dd6918a24de6c526277910e3766d8c2b1627c500b155f3eecad8fad65"}, + {file = "rpds_py-0.19.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cdb7eb3cf3deb3dd9e7b8749323b5d970052711f9e1e9f36364163627f96da58"}, + {file = "rpds_py-0.19.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26ab43b6d65d25b1a333c8d1b1c2f8399385ff683a35ab5e274ba7b8bb7dc61c"}, + {file = "rpds_py-0.19.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75130df05aae7a7ac171b3b5b24714cffeabd054ad2ebc18870b3aa4526eba23"}, + {file = "rpds_py-0.19.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c34f751bf67cab69638564eee34023909380ba3e0d8ee7f6fe473079bf93f09b"}, + {file = "rpds_py-0.19.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2671cb47e50a97f419a02cd1e0c339b31de017b033186358db92f4d8e2e17d8"}, + {file = "rpds_py-0.19.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c73254c256081704dba0a333457e2fb815364018788f9b501efe7c5e0ada401"}, + {file = "rpds_py-0.19.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4383beb4a29935b8fa28aca8fa84c956bf545cb0c46307b091b8d312a9150e6a"}, + {file = "rpds_py-0.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dbceedcf4a9329cc665452db1aaf0845b85c666e4885b92ee0cddb1dbf7e052a"}, + {file = "rpds_py-0.19.1-cp311-none-win32.whl", hash = "sha256:f0a6d4a93d2a05daec7cb885157c97bbb0be4da739d6f9dfb02e101eb40921cd"}, + {file = "rpds_py-0.19.1-cp311-none-win_amd64.whl", hash = "sha256:c149a652aeac4902ecff2dd93c3b2681c608bd5208c793c4a99404b3e1afc87c"}, + {file = "rpds_py-0.19.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:56313be667a837ff1ea3508cebb1ef6681d418fa2913a0635386cf29cff35165"}, + {file = "rpds_py-0.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d1d7539043b2b31307f2c6c72957a97c839a88b2629a348ebabe5aa8b626d6b"}, + {file = "rpds_py-0.19.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1dc59a5e7bc7f44bd0c048681f5e05356e479c50be4f2c1a7089103f1621d5"}, + {file = "rpds_py-0.19.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b8f78398e67a7227aefa95f876481485403eb974b29e9dc38b307bb6eb2315ea"}, + {file = "rpds_py-0.19.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ef07a0a1d254eeb16455d839cef6e8c2ed127f47f014bbda64a58b5482b6c836"}, + {file = "rpds_py-0.19.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8124101e92c56827bebef084ff106e8ea11c743256149a95b9fd860d3a4f331f"}, + {file = "rpds_py-0.19.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08ce9c95a0b093b7aec75676b356a27879901488abc27e9d029273d280438505"}, + {file = "rpds_py-0.19.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b02dd77a2de6e49078c8937aadabe933ceac04b41c5dde5eca13a69f3cf144e"}, + {file = "rpds_py-0.19.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4dd02e29c8cbed21a1875330b07246b71121a1c08e29f0ee3db5b4cfe16980c4"}, + {file = "rpds_py-0.19.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9c7042488165f7251dc7894cd533a875d2875af6d3b0e09eda9c4b334627ad1c"}, + {file = "rpds_py-0.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f809a17cc78bd331e137caa25262b507225854073fd319e987bd216bed911b7c"}, + {file = "rpds_py-0.19.1-cp312-none-win32.whl", hash = "sha256:3ddab996807c6b4227967fe1587febade4e48ac47bb0e2d3e7858bc621b1cace"}, + {file = "rpds_py-0.19.1-cp312-none-win_amd64.whl", hash = "sha256:32e0db3d6e4f45601b58e4ac75c6f24afbf99818c647cc2066f3e4b192dabb1f"}, + {file = "rpds_py-0.19.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:747251e428406b05fc86fee3904ee19550c4d2d19258cef274e2151f31ae9d38"}, + {file = "rpds_py-0.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dc733d35f861f8d78abfaf54035461e10423422999b360966bf1c443cbc42705"}, + {file = "rpds_py-0.19.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbda75f245caecff8faa7e32ee94dfaa8312a3367397975527f29654cd17a6ed"}, + {file = "rpds_py-0.19.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd04d8cab16cab5b0a9ffc7d10f0779cf1120ab16c3925404428f74a0a43205a"}, + {file = "rpds_py-0.19.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2d66eb41ffca6cc3c91d8387509d27ba73ad28371ef90255c50cb51f8953301"}, + {file = "rpds_py-0.19.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdf4890cda3b59170009d012fca3294c00140e7f2abe1910e6a730809d0f3f9b"}, + {file = "rpds_py-0.19.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1fa67ef839bad3815124f5f57e48cd50ff392f4911a9f3cf449d66fa3df62a5"}, + {file = "rpds_py-0.19.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b82c9514c6d74b89a370c4060bdb80d2299bc6857e462e4a215b4ef7aa7b090e"}, + {file = "rpds_py-0.19.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c7b07959866a6afb019abb9564d8a55046feb7a84506c74a6f197cbcdf8a208e"}, + {file = "rpds_py-0.19.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4f580ae79d0b861dfd912494ab9d477bea535bfb4756a2269130b6607a21802e"}, + {file = "rpds_py-0.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c6d20c8896c00775e6f62d8373aba32956aa0b850d02b5ec493f486c88e12859"}, + {file = "rpds_py-0.19.1-cp313-none-win32.whl", hash = "sha256:afedc35fe4b9e30ab240b208bb9dc8938cb4afe9187589e8d8d085e1aacb8309"}, + {file = "rpds_py-0.19.1-cp313-none-win_amd64.whl", hash = "sha256:1d4af2eb520d759f48f1073ad3caef997d1bfd910dc34e41261a595d3f038a94"}, + {file = "rpds_py-0.19.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:34bca66e2e3eabc8a19e9afe0d3e77789733c702c7c43cd008e953d5d1463fde"}, + {file = "rpds_py-0.19.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:24f8ae92c7fae7c28d0fae9b52829235df83f34847aa8160a47eb229d9666c7b"}, + {file = "rpds_py-0.19.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71157f9db7f6bc6599a852852f3389343bea34315b4e6f109e5cbc97c1fb2963"}, + {file = "rpds_py-0.19.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d494887d40dc4dd0d5a71e9d07324e5c09c4383d93942d391727e7a40ff810b"}, + {file = "rpds_py-0.19.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3661e6d4ba63a094138032c1356d557de5b3ea6fd3cca62a195f623e381c76"}, + {file = "rpds_py-0.19.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97fbb77eaeb97591efdc654b8b5f3ccc066406ccfb3175b41382f221ecc216e8"}, + {file = "rpds_py-0.19.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cc4bc73e53af8e7a42c8fd7923bbe35babacfa7394ae9240b3430b5dcf16b2a"}, + {file = "rpds_py-0.19.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:35af5e4d5448fa179fd7fff0bba0fba51f876cd55212f96c8bbcecc5c684ae5c"}, + {file = "rpds_py-0.19.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3511f6baf8438326e351097cecd137eb45c5f019944fe0fd0ae2fea2fd26be39"}, + {file = "rpds_py-0.19.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:57863d16187995c10fe9cf911b897ed443ac68189179541734502353af33e693"}, + {file = "rpds_py-0.19.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9e318e6786b1e750a62f90c6f7fa8b542102bdcf97c7c4de2a48b50b61bd36ec"}, + {file = "rpds_py-0.19.1-cp38-none-win32.whl", hash = "sha256:53dbc35808c6faa2ce3e48571f8f74ef70802218554884787b86a30947842a14"}, + {file = "rpds_py-0.19.1-cp38-none-win_amd64.whl", hash = "sha256:8df1c283e57c9cb4d271fdc1875f4a58a143a2d1698eb0d6b7c0d7d5f49c53a1"}, + {file = "rpds_py-0.19.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e76c902d229a3aa9d5ceb813e1cbcc69bf5bda44c80d574ff1ac1fa3136dea71"}, + {file = "rpds_py-0.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de1f7cd5b6b351e1afd7568bdab94934d656abe273d66cda0ceea43bbc02a0c2"}, + {file = "rpds_py-0.19.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fc5a84777cb61692d17988989690d6f34f7f95968ac81398d67c0d0994a897"}, + {file = "rpds_py-0.19.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:74129d5ffc4cde992d89d345f7f7d6758320e5d44a369d74d83493429dad2de5"}, + {file = "rpds_py-0.19.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e360188b72f8080fefa3adfdcf3618604cc8173651c9754f189fece068d2a45"}, + {file = "rpds_py-0.19.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13e6d4840897d4e4e6b2aa1443e3a8eca92b0402182aafc5f4ca1f5e24f9270a"}, + {file = "rpds_py-0.19.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f09529d2332264a902688031a83c19de8fda5eb5881e44233286b9c9ec91856d"}, + {file = "rpds_py-0.19.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0d4b52811dcbc1aba08fd88d475f75b4f6db0984ba12275d9bed1a04b2cae9b5"}, + {file = "rpds_py-0.19.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dd635c2c4043222d80d80ca1ac4530a633102a9f2ad12252183bcf338c1b9474"}, + {file = "rpds_py-0.19.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f35b34a5184d5e0cc360b61664c1c06e866aab077b5a7c538a3e20c8fcdbf90b"}, + {file = "rpds_py-0.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d4ec0046facab83012d821b33cead742a35b54575c4edfb7ed7445f63441835f"}, + {file = "rpds_py-0.19.1-cp39-none-win32.whl", hash = "sha256:f5b8353ea1a4d7dfb59a7f45c04df66ecfd363bb5b35f33b11ea579111d4655f"}, + {file = "rpds_py-0.19.1-cp39-none-win_amd64.whl", hash = "sha256:1fb93d3486f793d54a094e2bfd9cd97031f63fcb5bc18faeb3dd4b49a1c06523"}, + {file = "rpds_py-0.19.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7d5c7e32f3ee42f77d8ff1a10384b5cdcc2d37035e2e3320ded909aa192d32c3"}, + {file = "rpds_py-0.19.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:89cc8921a4a5028d6dd388c399fcd2eef232e7040345af3d5b16c04b91cf3c7e"}, + {file = "rpds_py-0.19.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca34e913d27401bda2a6f390d0614049f5a95b3b11cd8eff80fe4ec340a1208"}, + {file = "rpds_py-0.19.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5953391af1405f968eb5701ebbb577ebc5ced8d0041406f9052638bafe52209d"}, + {file = "rpds_py-0.19.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:840e18c38098221ea6201f091fc5d4de6128961d2930fbbc96806fb43f69aec1"}, + {file = "rpds_py-0.19.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d8b735c4d162dc7d86a9cf3d717f14b6c73637a1f9cd57fe7e61002d9cb1972"}, + {file = "rpds_py-0.19.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce757c7c90d35719b38fa3d4ca55654a76a40716ee299b0865f2de21c146801c"}, + {file = "rpds_py-0.19.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9421b23c85f361a133aa7c5e8ec757668f70343f4ed8fdb5a4a14abd5437244"}, + {file = "rpds_py-0.19.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3b823be829407393d84ee56dc849dbe3b31b6a326f388e171555b262e8456cc1"}, + {file = "rpds_py-0.19.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:5e58b61dcbb483a442c6239c3836696b79f2cd8e7eec11e12155d3f6f2d886d1"}, + {file = "rpds_py-0.19.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39d67896f7235b2c886fb1ee77b1491b77049dcef6fbf0f401e7b4cbed86bbd4"}, + {file = "rpds_py-0.19.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8b32cd4ab6db50c875001ba4f5a6b30c0f42151aa1fbf9c2e7e3674893fb1dc4"}, + {file = "rpds_py-0.19.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1c32e41de995f39b6b315d66c27dea3ef7f7c937c06caab4c6a79a5e09e2c415"}, + {file = "rpds_py-0.19.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1a129c02b42d46758c87faeea21a9f574e1c858b9f358b6dd0bbd71d17713175"}, + {file = "rpds_py-0.19.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:346557f5b1d8fd9966059b7a748fd79ac59f5752cd0e9498d6a40e3ac1c1875f"}, + {file = "rpds_py-0.19.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31e450840f2f27699d014cfc8865cc747184286b26d945bcea6042bb6aa4d26e"}, + {file = "rpds_py-0.19.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01227f8b3e6c8961490d869aa65c99653df80d2f0a7fde8c64ebddab2b9b02fd"}, + {file = "rpds_py-0.19.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69084fd29bfeff14816666c93a466e85414fe6b7d236cfc108a9c11afa6f7301"}, + {file = "rpds_py-0.19.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d2b88efe65544a7d5121b0c3b003ebba92bfede2ea3577ce548b69c5235185"}, + {file = "rpds_py-0.19.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ea961a674172ed2235d990d7edf85d15d8dfa23ab8575e48306371c070cda67"}, + {file = "rpds_py-0.19.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:5beffdbe766cfe4fb04f30644d822a1080b5359df7db3a63d30fa928375b2720"}, + {file = "rpds_py-0.19.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:720f3108fb1bfa32e51db58b832898372eb5891e8472a8093008010911e324c5"}, + {file = "rpds_py-0.19.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:c2087dbb76a87ec2c619253e021e4fb20d1a72580feeaa6892b0b3d955175a71"}, + {file = "rpds_py-0.19.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ddd50f18ebc05ec29a0d9271e9dbe93997536da3546677f8ca00b76d477680c"}, + {file = "rpds_py-0.19.1.tar.gz", hash = "sha256:31dd5794837f00b46f4096aa8ccaa5972f73a938982e32ed817bb520c465e520"}, +] + +[[package]] +name = "ruamel-yaml" +version = "0.18.6" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636"}, + {file = "ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b"}, +] + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} + +[package.extras] +docs = ["mercurial (>5.7)", "ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.8" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +optional = false +python-versions = ">=3.6" +files = [ + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, + {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, + {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, +] + +[[package]] +name = "scipy" +version = "1.14.0" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "scipy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7e911933d54ead4d557c02402710c2396529540b81dd554fc1ba270eb7308484"}, + {file = "scipy-1.14.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:687af0a35462402dd851726295c1a5ae5f987bd6e9026f52e9505994e2f84ef6"}, + {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:07e179dc0205a50721022344fb85074f772eadbda1e1b3eecdc483f8033709b7"}, + {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a9c9a9b226d9a21e0a208bdb024c3982932e43811b62d202aaf1bb59af264b1"}, + {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:076c27284c768b84a45dcf2e914d4000aac537da74236a0d45d82c6fa4b7b3c0"}, + {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42470ea0195336df319741e230626b6225a740fd9dce9642ca13e98f667047c0"}, + {file = "scipy-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:176c6f0d0470a32f1b2efaf40c3d37a24876cebf447498a4cefb947a79c21e9d"}, + {file = "scipy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ad36af9626d27a4326c8e884917b7ec321d8a1841cd6dacc67d2a9e90c2f0359"}, + {file = "scipy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d056a8709ccda6cf36cdd2eac597d13bc03dba38360f418560a93050c76a16e"}, + {file = "scipy-1.14.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f0a50da861a7ec4573b7c716b2ebdcdf142b66b756a0d392c236ae568b3a93fb"}, + {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:94c164a9e2498e68308e6e148646e486d979f7fcdb8b4cf34b5441894bdb9caf"}, + {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a7d46c3e0aea5c064e734c3eac5cf9eb1f8c4ceee756262f2c7327c4c2691c86"}, + {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eee2989868e274aae26125345584254d97c56194c072ed96cb433f32f692ed8"}, + {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3154691b9f7ed73778d746da2df67a19d046a6c8087c8b385bc4cdb2cfca74"}, + {file = "scipy-1.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c40003d880f39c11c1edbae8144e3813904b10514cd3d3d00c277ae996488cdb"}, + {file = "scipy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b083c8940028bb7e0b4172acafda6df762da1927b9091f9611b0bcd8676f2bc"}, + {file = "scipy-1.14.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff2438ea1330e06e53c424893ec0072640dac00f29c6a43a575cbae4c99b2b9"}, + {file = "scipy-1.14.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bbc0471b5f22c11c389075d091d3885693fd3f5e9a54ce051b46308bc787e5d4"}, + {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:64b2ff514a98cf2bb734a9f90d32dc89dc6ad4a4a36a312cd0d6327170339eb0"}, + {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:7d3da42fbbbb860211a811782504f38ae7aaec9de8764a9bef6b262de7a2b50f"}, + {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d91db2c41dd6c20646af280355d41dfa1ec7eead235642178bd57635a3f82209"}, + {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a01cc03bcdc777c9da3cfdcc74b5a75caffb48a6c39c8450a9a05f82c4250a14"}, + {file = "scipy-1.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65df4da3c12a2bb9ad52b86b4dcf46813e869afb006e58be0f516bc370165159"}, + {file = "scipy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:4c4161597c75043f7154238ef419c29a64ac4a7c889d588ea77690ac4d0d9b20"}, + {file = "scipy-1.14.0.tar.gz", hash = "sha256:b5923f48cb840380f9854339176ef21763118a7300a88203ccd0bdd26e58527b"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "setuptools" +version = "72.1.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, + {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, +] + +[package.extras] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.5" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, +] + +[[package]] +name = "sphinx" +version = "7.4.7" +description = "Python documentation generator" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, + {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, +] + +[package.dependencies] +alabaster = ">=0.7.14,<0.8.0" +babel = ">=2.13" +colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} +docutils = ">=0.20,<0.22" +imagesize = ">=1.3" +Jinja2 = ">=3.1" +packaging = ">=23.0" +Pygments = ">=2.17" +requests = ">=2.30.0" +snowballstemmer = ">=2.2" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" +tomli = {version = ">=2", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] + +[[package]] +name = "sphinx-autodoc-annotation" +version = "1.0-1" +description = "Use Python 3 annotations in sphinx-enabled docstrings" +optional = false +python-versions = "*" +files = [ + {file = "sphinx-autodoc-annotation-1.0-1.tar.gz", hash = "sha256:4a3d03081efe1e5f2bc9b9d00746550f45b9f543b0c79519c523168ca7f7d89a"}, +] + +[package.dependencies] +sphinx = ">=1.1" + +[[package]] +name = "sphinx-autodoc-typehints" +version = "2.2.3" +description = "Type hints (PEP 484) support for the Sphinx autodoc extension" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx_autodoc_typehints-2.2.3-py3-none-any.whl", hash = "sha256:b7058e8c5831e5598afca1a78fda0695d3291388d954464a6e480c36198680c0"}, + {file = "sphinx_autodoc_typehints-2.2.3.tar.gz", hash = "sha256:fde3d888949bd0a91207cf1e54afda58121dbb4bf1f183d0cc78a0826654c974"}, +] + +[package.dependencies] +sphinx = ">=7.3.5" + +[package.extras] +docs = ["furo (>=2024.1.29)"] +numpy = ["nptyping (>=2.5)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.4.4)", "defusedxml (>=0.7.1)", "diff-cover (>=9)", "pytest (>=8.1.1)", "pytest-cov (>=5)", "sphobjinv (>=2.3.1)", "typing-extensions (>=4.11)"] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b2" +description = "A modern skeleton for Sphinx themes." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, + {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, +] + +[package.dependencies] +sphinx = ">=4.0" + +[package.extras] +docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +description = "Add a copy button to each of your code cells." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"}, + {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"}, +] + +[package.dependencies] +sphinx = ">=1.8" + +[package.extras] +code-style = ["pre-commit (==2.12.1)"] +rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] + +[[package]] +name = "sphinx-jinja2-compat" +version = "0.3.0" +description = "Patches Jinja2 v3 to restore compatibility with earlier Sphinx versions." +optional = false +python-versions = ">=3.6" +files = [ + {file = "sphinx_jinja2_compat-0.3.0-py3-none-any.whl", hash = "sha256:b1e4006d8e1ea31013fa9946d1b075b0c8d2a42c6e3425e63542c1e9f8be9084"}, + {file = "sphinx_jinja2_compat-0.3.0.tar.gz", hash = "sha256:f3c1590b275f42e7a654e081db5e3e5fb97f515608422bde94015ddf795dfe7c"}, +] + +[package.dependencies] +jinja2 = ">=2.10" +markupsafe = ">=1" +standard-imghdr = {version = "3.10.14", markers = "python_version >= \"3.13\""} + +[[package]] +name = "sphinx-prompt" +version = "1.8.0" +description = "Sphinx directive to add unselectable prompt" +optional = false +python-versions = ">=3.9,<4.0" +files = [ + {file = "sphinx_prompt-1.8.0-py3-none-any.whl", hash = "sha256:369ecc633f0711886f9b3a078c83264245be1adf46abeeb9b88b5519e4b51007"}, + {file = "sphinx_prompt-1.8.0.tar.gz", hash = "sha256:47482f86fcec29662fdfd23e7c04ef03582714195d01f5d565403320084372ed"}, +] + +[package.dependencies] +docutils = "*" +pygments = "*" +Sphinx = ">=7.0.0,<8.0.0" + +[[package]] +name = "sphinx-tabs" +version = "3.4.5" +description = "Tabbed views for Sphinx" +optional = false +python-versions = "~=3.7" +files = [ + {file = "sphinx-tabs-3.4.5.tar.gz", hash = "sha256:ba9d0c1e3e37aaadd4b5678449eb08176770e0fc227e769b6ce747df3ceea531"}, + {file = "sphinx_tabs-3.4.5-py3-none-any.whl", hash = "sha256:92cc9473e2ecf1828ca3f6617d0efc0aa8acb06b08c56ba29d1413f2f0f6cf09"}, +] + +[package.dependencies] +docutils = "*" +pygments = "*" +sphinx = "*" + +[package.extras] +code-style = ["pre-commit (==2.13.0)"] +testing = ["bs4", "coverage", "pygments", "pytest (>=7.1,<8)", "pytest-cov", "pytest-regressions", "rinohtype"] + +[[package]] +name = "sphinx-toolbox" +version = "3.7.0" +description = "Box of handy tools for Sphinx 🧰 📔" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_toolbox-3.7.0-py3-none-any.whl", hash = "sha256:9ea800fb6b2ecc5f382ab3547b415029fe9603b0b7eeb4c15c5da77c1eb5f21a"}, + {file = "sphinx_toolbox-3.7.0.tar.gz", hash = "sha256:045db70e6d73241ff0b922f2e76597e2cc4904dbabef9fc6f3a46718b0e48d51"}, +] + +[package.dependencies] +apeye = ">=0.4.0" +autodocsumm = ">=0.2.0" +beautifulsoup4 = ">=4.9.1" +cachecontrol = {version = ">=0.13.0", extras = ["filecache"]} +dict2css = ">=0.2.3" +docutils = ">=0.16" +domdf-python-tools = ">=2.9.0" +filelock = ">=3.8.0" +html5lib = ">=1.1" +"ruamel.yaml" = ">=0.16.12" +sphinx = ">=3.2.0" +sphinx-autodoc-typehints = ">=1.11.1" +sphinx-jinja2-compat = ">=0.1.0" +sphinx-prompt = ">=1.1.0" +sphinx-tabs = ">=1.2.1,<3.5.0" +tabulate = ">=0.8.7" +typing-extensions = ">=3.7.4.3,<3.10.0.1 || >3.10.0.1" + +[package.extras] +all = ["coincidence (>=0.4.3)", "pygments (>=2.7.4,<=2.13.0)"] +testing = ["coincidence (>=0.4.3)", "pygments (>=2.7.4,<=2.13.0)"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["defusedxml (>=0.7.1)", "pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, +] + +[package.extras] +lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "standard-imghdr" +version = "3.10.14" +description = "Standard library imghdr redistribution. \"dead battery\"." +optional = false +python-versions = "*" +files = [ + {file = "standard_imghdr-3.10.14-py3-none-any.whl", hash = "sha256:cdf6883163349624dee9a81d2853a20260337c4cd41c04e99c082e01833a08e2"}, + {file = "standard_imghdr-3.10.14.tar.gz", hash = "sha256:2598fe2e7c540dbda34b233295e10957ab8dc8ac6f3bd9eaa8d38be167232e52"}, +] + +[[package]] +name = "sympy" +version = "1.13.1" +description = "Computer algebra system (CAS) in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8"}, + {file = "sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f"}, +] + +[package.dependencies] +mpmath = ">=1.1.0,<1.4" + +[package.extras] +dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tinycss2" +version = "1.3.0" +description = "A tiny CSS parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7"}, + {file = "tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d"}, +] + +[package.dependencies] +webencodings = ">=0.4" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["pytest", "ruff"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tornado" +version = "6.4.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.8" +files = [ + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "widgetsnbextension" +version = "4.0.11" +description = "Jupyter interactive widgets for Jupyter Notebook" +optional = false +python-versions = ">=3.7" +files = [ + {file = "widgetsnbextension-4.0.11-py3-none-any.whl", hash = "sha256:55d4d6949d100e0d08b94948a42efc3ed6dfdc0e9468b2c4b128c9a2ce3a7a36"}, + {file = "widgetsnbextension-4.0.11.tar.gz", hash = "sha256:8b22a8f1910bfd188e596fe7fc05dcbd87e810c8a4ba010bdb3da86637398474"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "9b837279be04212032b0d6f546e8a88348c3ad96b67caa86d6c3ed8fddd9afdb" diff --git a/pytket/pytket/_tket/circuit.pyi b/pytket/pytket/_tket/circuit.pyi index 1df443f3a9..2d52683b87 100644 --- a/pytket/pytket/_tket/circuit.pyi +++ b/pytket/pytket/_tket/circuit.pyi @@ -3636,9 +3636,9 @@ class RangePredicateOp(ClassicalEvalOp): """ A predicate defined by a range of values in binary encoding. """ - def __init__(self, width: int, upper: int, lower: int) -> None: + def __init__(self, width: int, lower: int, upper: int) -> None: """ - Construct from a bit width, an upper bound and a lower bound. + Construct from a bit width, a lower bound and an upper bound. """ @property def lower(self) -> int: diff --git a/pytket/pytket/_tket/passes.pyi b/pytket/pytket/_tket/passes.pyi index a764716485..20fc8db621 100644 --- a/pytket/pytket/_tket/passes.pyi +++ b/pytket/pytket/_tket/passes.pyi @@ -346,7 +346,7 @@ def DecomposeTK2(allow_swaps: bool = True, **kwargs: Any) -> BasePass: Gate fidelities can be passed as keyword arguments to perform noise-aware decompositions. If the fidelities of several gate types are provided, the best will be chosen. - We currently support `CX_fidelity`, `ZZMax_fidelity` and `ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities must be given by a single floating point fidelity. The `ZZPhase` fidelity is given as a lambda float -> float, mapping a ZZPhase angle parameter to its fidelity. These parameters will be used to return the optimal decomposition of each TK2 gate, taking noise into consideration. + We currently support `CX_fidelity`, `ZZMax_fidelity` and `ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities must be given by a single floating point fidelity. The `ZZPhase` fidelity is given as a lambda float -> float, mapping a ZZPhase angle parameter to its fidelity, or by a single float. These parameters will be used to return the optimal decomposition of each TK2 gate, taking noise into consideration. If no fidelities are provided, the TK2 gates will be decomposed exactly using CX gates. For equal fidelities, ZZPhase will be prefered over ZZMax and CX if the decomposition results in fewer two-qubit gates. diff --git a/pytket/pytket/_tket/transform.pyi b/pytket/pytket/_tket/transform.pyi index d033e46221..2a4d720607 100644 --- a/pytket/pytket/_tket/transform.pyi +++ b/pytket/pytket/_tket/transform.pyi @@ -140,7 +140,7 @@ class Transform: All TK2 gate parameters must be normalised, i.e. they must satisfy `NormalisedTK2Predicate`. - Gate fidelities are passed as keyword arguments to perform noise-aware decompositions. We currently support `CX_fidelity`, `ZZMax_fidelity` and `ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities must be given by a single floating point fidelity. The `ZZPhase` fidelity is given as a lambda float -> float, mapping a ZZPhase angle parameter to its fidelity. These parameters will be used to return the optimal decomposition of each TK2 gate, taking noise into consideration. + Gate fidelities are passed as keyword arguments to perform noise-aware decompositions. We currently support `CX_fidelity`, `ZZMax_fidelity` and `ZZPhase_fidelity`. If provided, the `CX` and `ZZMax` fidelities must be given by a single floating point fidelity. The `ZZPhase` fidelity is given as a lambda float -> float, mapping a ZZPhase angle parameter to its fidelity, or by a single float. These parameters will be used to return the optimal decomposition of each TK2 gate, taking noise into consideration. Using the `allow_swaps=True` (default) option, qubits will be swapped when convenient to reduce the two-qubit gate count of the decomposed TK2. diff --git a/pytket/pytket/_tket/unit_id.pyi b/pytket/pytket/_tket/unit_id.pyi index 249e18c544..31c0b96862 100644 --- a/pytket/pytket/_tket/unit_id.pyi +++ b/pytket/pytket/_tket/unit_id.pyi @@ -13,6 +13,10 @@ class Bit(UnitID): """ def __and__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: ... + def __copy__(self) -> Bit: + ... + def __deepcopy__(self, arg0: dict) -> Bit: + ... def __eq__(self, arg0: typing.Any) -> bool: ... def __getstate__(self) -> tuple: @@ -172,6 +176,10 @@ class Node(Qubit): """ Construct Node instance from JSON serializable list representation of the Node. """ + def __copy__(self) -> Node: + ... + def __deepcopy__(self, arg0: dict) -> Node: + ... @typing.overload def __init__(self, index: int) -> None: """ @@ -227,6 +235,10 @@ class Qubit(UnitID): """ Construct Qubit instance from JSON serializable list representation of the Qubit. """ + def __copy__(self) -> Qubit: + ... + def __deepcopy__(self, arg0: dict) -> Qubit: + ... def __getstate__(self) -> tuple: ... @typing.overload diff --git a/pytket/pytket/circuit/__init__.py b/pytket/pytket/circuit/__init__.py index 93be7f0831..510401369d 100644 --- a/pytket/pytket/circuit/__init__.py +++ b/pytket/pytket/circuit/__init__.py @@ -135,7 +135,7 @@ def overload_add_wasm_to_reg( if args_wasm is None: args_wasm = [0] - if filehandler._check_file: + if filehandler.checked: for reg in list_i: if reg.size > 32: raise ValueError( @@ -150,7 +150,11 @@ def overload_add_wasm_to_reg( please use only registers of at most 32 bits""" ) - if filehandler.check_function(funcname, len(list_i), len(list_o)): + # If the filehandler has not been checked we allow it to + # be added without checking the function arity. + if not filehandler.checked or filehandler.check_function( + funcname, len(list_i), len(list_o) + ): if (len(args_wasm)) > 0: self._add_w_register(max(args_wasm) + 1) return self._add_wasm( diff --git a/pytket/pytket/circuit/display/static/head_imports.html b/pytket/pytket/circuit/display/static/head_imports.html index d849e54df2..2fa047c2c7 100644 --- a/pytket/pytket/circuit/display/static/head_imports.html +++ b/pytket/pytket/circuit/display/static/head_imports.html @@ -2,4 +2,4 @@ - + diff --git a/pytket/pytket/qasm/qasm.py b/pytket/pytket/qasm/qasm.py index ecac3418cb..fab28a1f41 100644 --- a/pytket/pytket/qasm/qasm.py +++ b/pytket/pytket/qasm/qasm.py @@ -17,6 +17,7 @@ import re import uuid +import itertools from collections import OrderedDict from importlib import import_module from itertools import chain, groupby @@ -54,7 +55,7 @@ WASMOp, BarrierOp, ) -from pytket._tket.unit_id import _TEMP_BIT_NAME +from pytket._tket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE from pytket.circuit import ( Bit, BitRegister, @@ -1215,16 +1216,26 @@ def hqs_header(header: str) -> bool: return header in ["hqslib1", "hqslib1_dev"] +@dataclass +class ConditionString: + variable: str # variable, e.g. "c[1]" + comparator: str # comparator, e.g. "==" + value: int # value, e.g. "1" + + class LabelledStringList: """ Wrapper class for an ordered sequence of strings, where each string has a unique label, returned when the string is added, and a string may be removed from the sequence given its label. There is a method to retrieve the concatenation of all - strings in order. + strings in order. The conditions (e.g. "if(c[0]==1)") for some strings are stored + separately in `conditions`. These conditions will be converted to text when + retrieving the full string. """ def __init__(self) -> None: self.strings: OrderedDict[int, str] = OrderedDict() + self.conditions: Dict[int, ConditionString] = dict() self.label = 0 def add_string(self, string: str) -> int: @@ -1233,11 +1244,24 @@ def add_string(self, string: str) -> int: self.label += 1 return label + def get_string(self, label: int) -> Optional[str]: + return self.strings.get(label, None) + def del_string(self, label: int) -> None: self.strings.pop(label, None) def get_full_string(self) -> str: - return "".join(self.strings.values()) + strings = [] + for l, s in self.strings.items(): + condition = self.conditions.get(l) + if condition is not None: + strings.append( + f"if({condition.variable}{condition.comparator}{condition.value}) " + + s + ) + else: + strings.append(s) + return "".join(strings) def make_params_str(params: Optional[List[Union[float, Expr]]]) -> str: @@ -1277,6 +1301,42 @@ def make_args_str(args: List[UnitID]) -> str: return s +@dataclass +class ScratchPredicate: + variable: str # variable, e.g. "c[1]" + comparator: str # comparator, e.g. "==" + value: int # value, e.g. "1" + dest: str # destination bit, e.g. "tk_SCRATCH_BIT[0]" + + +def _vars_overlap(v: str, w: str) -> bool: + """check if two variables have overlapping bits""" + v_split = v.split("[") + w_split = w.split("[") + if v_split[0] != w_split[0]: + # different registers + return False + # e.g. (a[1], a), (a, a[1]), (a[1], a[1]), (a, a) + return len(v_split) != len(w_split) or v == w + + +def _var_appears(v: str, s: str) -> bool: + """check if variable v appears in string s""" + v_split = v.split("[") + if len(v_split) == 1: + # check if v appears in s and is not surrounded by word characters + # e.g. a = a & b or a = a[1] & b[1] + return bool(re.search(r"(? Bit: + self.scratch_reg = BitRegister(self.scratch_reg.name, self.scratch_reg.size + 1) + return Bit(self.scratch_reg.name, self.scratch_reg.size - 1) + + def remove_last_scratch_bit(self) -> None: + assert self.scratch_reg.size > 0 + self.scratch_reg = BitRegister(self.scratch_reg.name, self.scratch_reg.size - 1) + def write_params(self, params: Optional[List[Union[float, Expr]]]) -> None: params_str = make_params_str(params) self.strings.add_string(params_str) @@ -1392,18 +1463,11 @@ def make_gate_definition( s += "}\n" return s - def mark_as_written(self, written_variable: str) -> None: - """Remove any references to the written-to variable in `self.range_preds`, so - that we don't try and replace instances of the variable with stale aliases. - """ - hits = [ - (variable, comparator, value, dest_bit, label) - for (variable, comparator, value, dest_bit, label) in self.range_preds - if variable == written_variable - or written_variable.startswith(variable + "[") - ] - for hit in hits: - self.range_preds.remove(hit) + def mark_as_written(self, label: int, written_variable: str) -> None: + if label in self.variable_writes: + self.variable_writes[label].append(written_variable) + else: + self.variable_writes[label] = [written_variable] def add_range_predicate(self, op: RangePredicateOp, args: List[Bit]) -> None: comparator, value = _parse_range(op.lower, op.upper, self.maxwidth) @@ -1438,35 +1502,91 @@ def add_range_predicate(self, op: RangePredicateOp, args: List[Bit]) -> None: # (variable, comparator, value), provided that variable hasn't been written to # in the mean time. (So we must watch for that, and remove the record from the # list if it is.) - self.range_preds.append((variable, comparator, value, dest_bit, label)) - - def condition_string(self, op: Conditional, variable: str) -> str: - # Check whether the variable is actually in `self.range_preds`. - hits = [ - (real_variable, comparator, value, label) - for (real_variable, comparator, value, dest_bit, label) in self.range_preds - if dest_bit == variable - ] - if not hits: - return f"if({variable}=={op.value}) " - else: - assert len(hits) == 1 - real_variable, comparator, value, label = hits[0] - self.strings.del_string(label) - if op.value == 1: - return f"if({real_variable}{comparator}{value}) " - else: - assert op.value == 0 - return f"if({real_variable}{_negate_comparator(comparator)}{value}) " + # Note that we only perform such rewrites for internal scratch bits. + if dest_bit.startswith(_TEMP_BIT_NAME): + self.range_preds[label] = ScratchPredicate( + variable, comparator, value, dest_bit + ) + + def replace_condition(self, pred_label: int) -> bool: + """Given the label of a predicate p=(var, comp, value, dest, label) + we scan the lines after p: + 1.if dest is the condition of a conditional line we replace dest with + the predicate and do 2 for the inner command. + 2.if either the variable or the dest gets written, we stop. + returns true if a replacement is made. + """ + assert pred_label in self.range_preds + success = False + pred = self.range_preds[pred_label] + line_labels = [] + for label in range(pred_label + 1, self.strings.label): + string = self.strings.get_string(label) + if string is None: + continue + line_labels.append(label) + if "\n" not in string: + continue + written_variables: List[str] = [] + # (label, condition) + conditions: List[Tuple[int, ConditionString]] = [] + for l in line_labels: + written_variables.extend(self.variable_writes.get(l, [])) + cond = self.strings.conditions.get(l) + if cond: + conditions.append((l, cond)) + if len(conditions) == 1 and pred.dest == conditions[0][1].variable: + # if the condition is dest, replace the condition with pred + success = True + if conditions[0][1].value == 1: + self.strings.conditions[conditions[0][0]] = ConditionString( + pred.variable, pred.comparator, pred.value + ) + else: + assert conditions[0][1].value == 0 + self.strings.conditions[conditions[0][0]] = ConditionString( + pred.variable, + _negate_comparator(pred.comparator), + pred.value, + ) + if any(_vars_overlap(pred.dest, v) for v in written_variables) or any( + _vars_overlap(pred.variable, v) for v in written_variables + ): + return success + line_labels.clear() + conditions.clear() + written_variables.clear() + return success + + def remove_unused_predicate(self, pred_label: int) -> bool: + """Given the label of a predicate p=(var, comp, value, dest, label), + we remove p if dest never appears after p.""" + assert pred_label in self.range_preds + pred = self.range_preds[pred_label] + for label in range(pred_label + 1, self.strings.label): + string = self.strings.get_string(label) + if string is None: + continue + if ( + _var_appears(pred.dest, string) + or label in self.strings.conditions + and _vars_overlap(pred.dest, self.strings.conditions[label].variable) + ): + return False + self.range_preds.pop(pred_label) + self.strings.del_string(pred_label) + return True def add_conditional(self, op: Conditional, args: List[UnitID]) -> None: - bits = args[: op.width] - control_bit = bits[0] + control_bits = args[: op.width] if op.width == 1 and hqs_header(self.header): - variable = str(control_bit) + variable = str(control_bits[0]) else: - variable = control_bit.reg_name - if hqs_header(self.header) and bits != self.cregs[variable].to_list(): + variable = control_bits[0].reg_name + if ( + hqs_header(self.header) + and control_bits != self.cregs[variable].to_list() + ): raise QASMUnsupportedError( "hqslib1 QASM conditions must be an entire classical " "register or a single bit" @@ -1476,16 +1596,44 @@ def add_conditional(self, op: Conditional, args: List[UnitID]) -> None: raise QASMUnsupportedError( "OpenQASM conditions must be an entire classical register" ) - if bits != self.cregs[variable].to_list(): + if control_bits != self.cregs[variable].to_list(): raise QASMUnsupportedError( "OpenQASM conditions must be a single classical register" ) if op.op.type == OpType.Phase: # Conditional phase is ignored. return - condstr = self.condition_string(op, variable) - self.strings.add_string(condstr) + if op.op.type == OpType.RangePredicate: + raise QASMUnsupportedError( + "Conditional RangePredicate is currently unsupported." + ) + # we assign the condition to a scratch bit, which we will later remove + # if the condition variable is unchanged. + scratch_bit = self.fresh_scratch_bit() + pred_label = self.strings.add_string( + f"if({variable}=={op.value}) " + f"{scratch_bit} = 1;\n" + ) + self.range_preds[pred_label] = ScratchPredicate( + variable, "==", op.value, str(scratch_bit) + ) + # we will later add condition to all lines starting from next_label + next_label = self.strings.label self.add_op(op.op, args[op.width :]) + # add conditions to the lines after the predicate + is_new_line = True + for label in range(next_label, self.strings.label): + string = self.strings.get_string(label) + assert string is not None + if is_new_line and string != "\n": + self.strings.conditions[label] = ConditionString( + str(scratch_bit), "==", 1 + ) + is_new_line = "\n" in string + if self.replace_condition(pred_label) and self.remove_unused_predicate( + pred_label + ): + # remove the unused scratch bit + self.remove_last_scratch_bit() def add_set_bits(self, op: SetBitsOp, args: List[Bit]) -> None: creg_name = args[0].reg_name @@ -1493,12 +1641,12 @@ def add_set_bits(self, op: SetBitsOp, args: List[Bit]) -> None: # check if whole register can be set at once if bits == tuple(self.cregs[creg_name].to_list()): value = int("".join(map(str, map(int, vals[::-1]))), 2) - self.strings.add_string(f"{creg_name} = {value};\n") - self.mark_as_written(f"{creg_name}") + label = self.strings.add_string(f"{creg_name} = {value};\n") + self.mark_as_written(label, f"{creg_name}") else: for bit, value in zip(bits, vals): - self.strings.add_string(f"{bit} = {int(value)};\n") - self.mark_as_written(f"{bit}") + label = self.strings.add_string(f"{bit} = {int(value)};\n") + self.mark_as_written(label, f"{bit}") def add_copy_bits(self, op: CopyBitsOp, args: List[Bit]) -> None: l_args = args[op.n_inputs :] @@ -1510,12 +1658,12 @@ def add_copy_bits(self, op: CopyBitsOp, args: List[Bit]) -> None: l_args == self.cregs[l_name].to_list() and r_args == self.cregs[r_name].to_list() ): - self.strings.add_string(f"{l_name} = {r_name};\n") - self.mark_as_written(f"{l_name}") + label = self.strings.add_string(f"{l_name} = {r_name};\n") + self.mark_as_written(label, f"{l_name}") else: for bit_l, bit_r in zip(l_args, r_args): - self.strings.add_string(f"{bit_l} = {bit_r};\n") - self.mark_as_written(f"{bit_l}") + label = self.strings.add_string(f"{bit_l} = {bit_r};\n") + self.mark_as_written(label, f"{bit_l}") def add_multi_bit(self, op: MultiBitOp, args: List[Bit]) -> None: assert len(args) >= 2 @@ -1530,24 +1678,26 @@ def add_explicit_op(self, op: Op, args: List[Bit]) -> None: opstr = str(op) if opstr not in _classical_gatestr_map: raise QASMUnsupportedError(f"Classical gate {opstr} not supported.") - self.strings.add_string( + label = self.strings.add_string( f"{args[-1]} = {args[0]} {_classical_gatestr_map[opstr]} {args[1]};\n" ) - self.mark_as_written(f"{args[-1]}") + self.mark_as_written(label, f"{args[-1]}") def add_classical_exp_box(self, op: ClassicalExpBox, args: List[Bit]) -> None: out_args = args[op.get_n_i() :] if len(out_args) == 1: - self.strings.add_string(f"{out_args[0]} = {str(op.get_exp())};\n") - self.mark_as_written(f"{out_args[0]}") + label = self.strings.add_string(f"{out_args[0]} = {str(op.get_exp())};\n") + self.mark_as_written(label, f"{out_args[0]}") elif ( out_args == self.cregs[out_args[0].reg_name].to_list()[ : op.get_n_io() + op.get_n_o() ] ): - self.strings.add_string(f"{out_args[0].reg_name} = {str(op.get_exp())};\n") - self.mark_as_written(f"{out_args[0].reg_name}") + label = self.strings.add_string( + f"{out_args[0].reg_name} = {str(op.get_exp())};\n" + ) + self.mark_as_written(label, f"{out_args[0].reg_name}") else: raise QASMUnsupportedError( f"ClassicalExpBox only supported" @@ -1566,14 +1716,14 @@ def add_wasm(self, op: WASMOp, args: List[Bit]) -> None: QASMUnsupportedError("WASM ops must act on entire registers.") reglist.append(regname) if outputs: - self.strings.add_string(f"{', '.join(outputs)} = ") + label = self.strings.add_string(f"{', '.join(outputs)} = ") self.strings.add_string(f"{op.func_name}({', '.join(inputs)});\n") for variable in outputs: - self.mark_as_written(variable) + self.mark_as_written(label, variable) def add_measure(self, args: List[UnitID]) -> None: - self.strings.add_string(f"measure {args[0]} -> {args[1]};\n") - self.mark_as_written(f"{args[1]}") + label = self.strings.add_string(f"measure {args[0]} -> {args[1]};\n") + self.mark_as_written(label, f"{args[1]}") def add_zzphase(self, param: Union[float, Expr], args: List[UnitID]) -> None: # as op.params returns reduced parameters, we can assume @@ -1693,10 +1843,28 @@ def add_op(self, op: Op, args: List[UnitID]) -> None: ) def finalize(self) -> str: + # try removing unused predicates + pred_labels = list(self.range_preds.keys()) + for label in pred_labels: + # try replacing conditions with a predicate + self.replace_condition(label) + # try removing the predicate + self.remove_unused_predicate(label) + reg_strings = LabelledStringList() + for reg in self.qregs.values(): + reg_strings.add_string(f"qreg {reg.name}[{reg.size}];\n") + for bit_reg in self.cregs.values(): + reg_strings.add_string(f"creg {bit_reg.name}[{bit_reg.size}];\n") + if self.scratch_reg.size > 0: + reg_strings.add_string( + f"creg {self.scratch_reg.name}[{self.scratch_reg.size}];\n" + ) return ( self.prefix + self.gatedefs - + _filtered_qasm_str(self.strings.get_full_string()) + + _filtered_qasm_str( + reg_strings.get_full_string() + self.strings.get_full_string() + ) ) diff --git a/pytket/pytket/wasm/wasm.py b/pytket/pytket/wasm/wasm.py index 3c107523e4..bf9de1a538 100644 --- a/pytket/pytket/wasm/wasm.py +++ b/pytket/pytket/wasm/wasm.py @@ -15,6 +15,8 @@ from os.path import exists import base64 import hashlib +from functools import cached_property +from typing_extensions import deprecated from qwasm import ( # type: ignore decode_module, @@ -29,10 +31,14 @@ ) -class WasmFileHandler: - """Add a wasm file to your workflow, stores a copy of the file and - checks the function signatures of the file. Offers function to add - a wasm op to a circuit""" +class WasmModuleHandler: + """Construct and optionally check a wasm module for use in wasm Ops.""" + + checked: bool + _int_size: int + _wasm_module: bytes + _functions: dict[str, tuple[int, int]] + _unsupported_functions: list[str] type_lookup = { LANG_TYPE_I32: "i32", @@ -42,15 +48,17 @@ class WasmFileHandler: LANG_TYPE_EMPTY: None, } - def __init__(self, filepath: str, check_file: bool = True, int_size: int = 32): + def __init__( + self, wasm_module: bytes, check: bool = True, int_size: int = 32 + ) -> None: """ - Construct a wasm file handler + Construct a wasm module handler - :param filepath: Path to the wasm file - :type filepath: str - :param check_file: If ``True`` checks file for compatibility with wasm + :param wasm_module: A wasm module in binary format. + :type wasm_module: bytes + :param check: If ``True`` checks file for compatibility with wasm standards. If ``False`` checks are skipped. - :type check_file: bool + :type check: bool :param int_size: length of the integer that is used in the wasm file :type int_size: int """ @@ -64,169 +72,190 @@ def __init__(self, filepath: str, check_file: bool = True, int_size: int = 32): "given integer length not valid, only 32 and 64 are allowed" ) - self._filepath = filepath - - function_signatures: list = [] - function_names: list = [] - _func_lookup = {} - - if not exists(self._filepath): - raise ValueError("wasm file not found at given path") + # stores the names of the functions mapped + # to the number of parameters and the number of return values + self._functions = dict() - with open(self._filepath, "rb") as file: - self._wasm_file: bytes = file.read() + # contains the list of functions that are not allowed + # to use in pytket (because of types that are not integers + # of the supplied int_size.) + self._unsupported_functions = [] - self._wasm_file_encoded = base64.b64encode(self._wasm_file) + self._wasm_module = wasm_module + self.checked = False - self._wasmfileuid = hashlib.sha256(self._wasm_file_encoded).hexdigest() + if check: + self.check() - self._check_file = check_file + def check(self) -> None: + """Collect functions from the module that can be used with pytket. - # stores the names of the functions mapped - # to the number of parameters and the number of return values - self._functions = dict() + Populates the internal list of supported and unsupported functions + and marks the module as checked so that subsequent checking is not + required. + """ + if self.checked: + return - # contains the list of functions that are not allowed - # to use in pytket (because of types that are not i32) - self._unsupported_function = [] - - if self._check_file: - mod_iter = iter(decode_module(self._wasm_file)) - _, _ = next(mod_iter) - - for _, cur_sec_data in mod_iter: - # read in list of function signatures - if cur_sec_data.id == SEC_TYPE: - for idx, entry in enumerate(cur_sec_data.payload.entries): - function_signatures.append({}) - function_signatures[idx]["parameter_types"] = [ - self.type_lookup[pt] for pt in entry.param_types - ] - if entry.return_count > 1: - if ( - isinstance(entry.return_type, list) - and len(entry.return_type) == entry.return_count - ): - function_signatures[idx]["return_types"] = [ - self.type_lookup[rt] for rt in entry.return_type - ] - elif isinstance(entry.return_type, int): - function_signatures[idx]["return_types"] = [ - self.type_lookup[entry.return_type] - ] * entry.return_count - else: - raise ValueError( - f"Only parameter and return values of " - + f"i{self._int_size} types are" - + f" allowed, found type: {entry.return_type}" - ) - elif entry.return_count == 1: + function_signatures: list = [] + function_names: list = [] + _func_lookup = {} + mod_iter = iter(decode_module(self._wasm_module)) + _, _ = next(mod_iter) + + for _, cur_sec_data in mod_iter: + # read in list of function signatures + if cur_sec_data.id == SEC_TYPE: + for idx, entry in enumerate(cur_sec_data.payload.entries): + function_signatures.append({}) + function_signatures[idx]["parameter_types"] = [ + self.type_lookup[pt] for pt in entry.param_types + ] + if entry.return_count > 1: + if ( + isinstance(entry.return_type, list) + and len(entry.return_type) == entry.return_count + ): function_signatures[idx]["return_types"] = [ - self.type_lookup[entry.return_type] + self.type_lookup[rt] for rt in entry.return_type ] + elif isinstance(entry.return_type, int): + function_signatures[idx]["return_types"] = [ + self.type_lookup[entry.return_type] + ] * entry.return_count else: - function_signatures[idx]["return_types"] = [] - - # read in list of function names - elif cur_sec_data.id == SEC_EXPORT: - f_idx = 0 - for _, entry in enumerate(cur_sec_data.payload.entries): - if entry.kind == 0: - f_name = entry.field_str.tobytes().decode() - function_names.append(f_name) - _func_lookup[f_name] = (f_idx, entry.index) - f_idx += 1 - - # read in map of function signatures to function names - elif cur_sec_data.id == SEC_FUNCTION: - self._function_types = cur_sec_data.payload.types - - for x in function_names: - # check for only integer type in parameters and return values - supported_function = True - idx = _func_lookup[x][1] - - if idx >= len(self._function_types): - raise ValueError("invalid wasm file") - - for t in function_signatures[self._function_types[idx]][ - "parameter_types" - ]: - if t != self._int_type: - supported_function = False - for t in function_signatures[self._function_types[idx]]["return_types"]: - if t != self._int_type: - supported_function = False - - if ( - len(function_signatures[self._function_types[idx]]["return_types"]) - > 1 - ): + raise ValueError( + f"Only parameter and return values of " + + f"i{self._int_size} types are" + + f" allowed, found type: {entry.return_type}" + ) + elif entry.return_count == 1: + function_signatures[idx]["return_types"] = [ + self.type_lookup[entry.return_type] + ] + else: + function_signatures[idx]["return_types"] = [] + + # read in list of function names + elif cur_sec_data.id == SEC_EXPORT: + f_idx = 0 + for _, entry in enumerate(cur_sec_data.payload.entries): + if entry.kind == 0: + f_name = entry.field_str.tobytes().decode() + function_names.append(f_name) + _func_lookup[f_name] = (f_idx, entry.index) + f_idx += 1 + + # read in map of function signatures to function names + elif cur_sec_data.id == SEC_FUNCTION: + self._function_types = cur_sec_data.payload.types + + for x in function_names: + # check for only integer type in parameters and return values + supported_function = True + idx = _func_lookup[x][1] + + if idx >= len(self._function_types): + raise ValueError("invalid wasm file") + + for t in function_signatures[self._function_types[idx]]["parameter_types"]: + if t != self._int_type: + supported_function = False + for t in function_signatures[self._function_types[idx]]["return_types"]: + if t != self._int_type: supported_function = False - if supported_function: - self._functions[x] = ( - len( - function_signatures[self._function_types[idx]][ - "parameter_types" - ] - ), - len( - function_signatures[self._function_types[idx]][ - "return_types" - ] - ), - ) + if len(function_signatures[self._function_types[idx]]["return_types"]) > 1: + supported_function = False - if not supported_function: - self._unsupported_function.append(x) + if supported_function: + self._functions[x] = ( + len( + function_signatures[self._function_types[idx]][ + "parameter_types" + ] + ), + len(function_signatures[self._function_types[idx]]["return_types"]), + ) - if "init" not in self._functions: - raise ValueError("wasm file needs to contain a function named 'init'") + if not supported_function: + self._unsupported_functions.append(x) - if self._functions["init"][0] != 0: - raise ValueError("init function should not have any parameter") + if "init" not in self._functions: + raise ValueError("wasm file needs to contain a function named 'init'") - if self._functions["init"][1] != 0: - raise ValueError("init function should not have any results") + if self._functions["init"][0] != 0: + raise ValueError("init function should not have any parameter") - def __str__(self) -> str: - """str representation of the wasm file""" - return self._wasmfileuid + if self._functions["init"][1] != 0: + raise ValueError("init function should not have any results") - def __repr__(self) -> str: - """str representation of the contents of the wasm file""" - if self._check_file: - result = f"Functions in wasm file with the uid {self._wasmfileuid}:\n" - for x in self._functions: - result += f"function '{x}' with " - result += f"{self._functions[x][0]} i{self._int_size} parameter(s)" - result += ( - f" and {self._functions[x][1]} i{self._int_size} return value(s)\n" - ) + # Mark the module as checked, which indicates that function + # signatures are available and that it does not need + # to be checked again. + self.checked = True - for x in self._unsupported_function: - result += ( - f"unsupported function with invalid " - f"parameter or result type: '{x}' \n" - ) + @property + @deprecated("Use public property `checked` instead.") + def _check_file(self) -> bool: + return self.checked - return result - else: - raise ValueError( - """the content of the wasm file can only be printed if the -wasm file was checked""" + def __str__(self) -> str: + """str representation of the wasm module""" + return self.uid + + def __repr__(self) -> str: + """str representation of the contents of the wasm file.""" + if not self.checked: + return f"Unchecked wasm module file with the uid {self.uid}" + + result = f"Functions in wasm file with the uid {self.uid}:\n" + for x in self.functions: + result += f"function '{x}' with " + result += f"{self.functions[x][0]} i{self._int_size} parameter(s)" + result += f" and {self.functions[x][1]} i{self._int_size} return value(s)\n" + + for x in self.unsupported_functions: + result += ( + f"unsupported function with invalid " + f"parameter or result type: '{x}' \n" ) + return result + def bytecode(self) -> bytes: - """The WASM content as bytecode""" - return self._wasm_file + """The wasm content as bytecode""" + return self._wasm_module + + @cached_property + def bytecode_base64(self) -> bytes: + """The wasm content as base64 encoded bytecode.""" + return base64.b64encode(self._wasm_module) + + @property + @deprecated("Use public property `bytecode_base64` instead.") + def _wasm_file_encoded(self) -> bytes: + return self.bytecode_base64 + + @cached_property + def uid(self) -> str: + """A unique identifier for the module calculated from its' checksum.""" + return hashlib.sha256(self.bytecode_base64).hexdigest() + + @property + @deprecated("Use public property `uid` instead.") + def _wasmfileuid(self) -> str: + return self.uid def check_function( self, function_name: str, number_of_parameters: int, number_of_returns: int ) -> bool: """ - Checks a given function name and signature if it is included + Checks a given function name and signature if it is included and the + module has previously been checked. + + If the module has not been checked this function with will raise a + ValueError. :param function_name: name of the function that is checked :type function_name: str @@ -235,9 +264,67 @@ def check_function( :param number_of_returns: number of integer return values of the function :type number_of_returns: int :return: true if the signature and the name of the function is correct""" + if not self.checked: + raise ValueError( + "Cannot retrieve functions from an unchecked wasm module." + " Please call .check() first." + ) - return not self._check_file or ( + return ( (function_name in self._functions) and (self._functions[function_name][0] == number_of_parameters) and (self._functions[function_name][1] == number_of_returns) ) + + @property + def functions(self) -> dict[str, tuple[int, int]]: + """Retrieve the names of functions with the number of input and out arguments. + + If the module has not been checked this function with will raise a + ValueError. + """ + if not self.checked: + raise ValueError( + "Cannot retrieve functions from an unchecked wasm module." + " Please call .check() first." + ) + return self._functions + + @property + def unsupported_functions(self) -> list[str]: + """Retrieve the names of unsupported functions as a list of strings. + + If the module has not been checked this function with will raise a + ValueError. + """ + if not self.checked: + raise ValueError( + "Cannot retrieve functions from an unchecked wasm module." + " Please call .check() first." + ) + return self._unsupported_functions + + +class WasmFileHandler(WasmModuleHandler): + """Construct and optionally check a wasm module from a file for use in wasm Ops.""" + + def __init__(self, filepath: str, check_file: bool = True, int_size: int = 32): + """ + Construct a wasm file handler using a filepath to read a wasm module into + memory. + + :param filepath: Path to the wasm file + :type filepath: str + :param check_file: If ``True`` checks file for compatibility with wasm + standards. If ``False`` checks are skipped. + :type check_file: bool + :param int_size: length of the integer that is used in the wasm file + :type int_size: int + """ + if not exists(filepath): + raise ValueError("wasm file not found at given path") + + with open(filepath, "rb") as file: + self._wasm_file: bytes = file.read() + + super().__init__(self._wasm_file, check_file, int_size) diff --git a/pytket/tests/classical_test.py b/pytket/tests/classical_test.py index 8d5ed1fd4d..329c6d8ea8 100644 --- a/pytket/tests/classical_test.py +++ b/pytket/tests/classical_test.py @@ -72,6 +72,8 @@ from pytket.passes import DecomposeClassicalExp, FlattenRegisters +from sympy import Symbol + from strategies import reg_name_regex, binary_digits, uint32, uint64 # type: ignore curr_file_path = Path(__file__).resolve().parent @@ -538,14 +540,16 @@ def test_wasmfilehandler_invalid_file_1_e_64_no_check() -> None: ) -def test_wasmfilehandler_invalid_file_1_e_32_no_check_repr() -> None: +def test_wasmfilehandler_invalid_file_1_e_32_unchecked_repr() -> None: w = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-2-emcc.wasm", int_size=32, check_file=False, ) - with pytest.raises(ValueError): + assert ( repr(w) + == "Unchecked wasm module file with the uid 139e7266b9dcc32dc5237ac2a43d8883847dee3c792e147528fae3984229cc5d" + ) def test_wasmfilehandler_repr() -> None: @@ -1283,6 +1287,12 @@ def test_classical_ops() -> None: assert ceb.get_exp() == op2.get_exp() +def test_range_predicate_properties() -> None: + range_predicate = RangePredicateOp(width=8, lower=3, upper=5) + assert range_predicate.lower == 3 + assert range_predicate.upper == 5 + + def test_add_expbox_bug() -> None: # previously a bug where if IO args weren't # at the back of the input iterator, the op signature @@ -1481,5 +1491,14 @@ def test_box_equality_check() -> None: assert ceb1 == ClassicalExpBox(2, 0, 1, exp1) +def test_sym_sub_range_pred() -> None: + c = Circuit(1, 2) + c.H(0, condition=reg_eq(BitRegister("c", 2), 3)) + c1 = c.copy() + c.symbol_substitution({Symbol("a"): 0.5}) + + assert c == c1 + + if __name__ == "__main__": test_wasm() diff --git a/pytket/tests/passes_serialisation_test.py b/pytket/tests/passes_serialisation_test.py index 242f2f33cf..cf7423248c 100644 --- a/pytket/tests/passes_serialisation_test.py +++ b/pytket/tests/passes_serialisation_test.py @@ -299,6 +299,18 @@ def nonparam_predicate_dict(name: str) -> Dict[str, Any]: "AutoRebase": standard_pass_dict( {"name": "AutoRebase", "basis_allowed": ["H", "TK1", "CX"], "allow_swaps": True} ), + # ZZPhase must be a float and not a function. + "DecomposeTK2": standard_pass_dict( + { + "name": "DecomposeTK2", + "fidelities": { + "CX": None, + "ZZMax": 1.0, + "ZZPhase": 0.5, + }, + "allow_swaps": True, + } + ), } # non-parametrized passes that satisfy pass.from_dict(d).to_dict()==d diff --git a/pytket/tests/qasm_test.py b/pytket/tests/qasm_test.py index 5652ae6b8e..b848128214 100644 --- a/pytket/tests/qasm_test.py +++ b/pytket/tests/qasm_test.py @@ -35,6 +35,7 @@ if_not_bit, BitRegister, CustomGate, + RangePredicateOp, ) from pytket.circuit.decompose_classical import DecomposeClassicalError from pytket.circuit.logic_exp import BitWiseOp, create_bit_logic_exp @@ -955,6 +956,87 @@ def test_const_condition() -> None: ) +def test_conditional_multi_line_ops() -> None: + # https://github.com/CQCL/tket/issues/1491 + c = Circuit(0, 3) + c.add_c_setbits(values=[True, True], args=[Bit(1), Bit(2)], condition=Bit(0)) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +creg c[3]; +if(c[0]==1) c[1] = 1; +if(c[0]==1) c[2] = 1; +""" + ) + c = Circuit(0, 5) + c.add_c_copybits( + args_in=[Bit(1), Bit(2)], args_out=[Bit(3), Bit(4)], condition=Bit(0) + ) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +creg c[5]; +if(c[0]==1) c[3] = c[1]; +if(c[0]==1) c[4] = c[2]; +""" + ) + c = Circuit(0, 3) + c.add_c_setbits(values=[True, True], args=[Bit(0), Bit(1)], condition=Bit(0)) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +creg c[3]; +creg tk_SCRATCH_BITREG_0[1]; +if(c[0]==1) tk_SCRATCH_BITREG_0[0] = 1; +if(c[0]==1) c[0] = 1; +if(tk_SCRATCH_BITREG_0[0]==1) c[1] = 1; +""" + ) + c = Circuit(1, 3) + c.add_c_setbits( + values=[True, True], + args=[Bit(0), Bit(1)], + condition_bits=[Bit(0), Bit(1), Bit(2)], + condition_value=3, + ) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +qreg q[1]; +creg c[3]; +creg tk_SCRATCH_BITREG_0[1]; +if(c==3) tk_SCRATCH_BITREG_0[0] = 1; +if(c==3) c[0] = 1; +if(tk_SCRATCH_BITREG_0[0]==1) c[1] = 1; +""" + ) + + +def test_conditional_range_predicate() -> None: + range_predicate = RangePredicateOp(6, 0, 27) + c = Circuit(0, 8) + c.add_gate(range_predicate, [0, 1, 2, 3, 4, 5, 6], condition=Bit(7)) + # remove once https://github.com/CQCL/tket/issues/1508 + # is resolved + with pytest.raises(Exception) as errorinfo: + circuit_to_qasm_str(c, header="hqslib1") + assert "Conditional RangePredicate is currently unsupported." in str( + errorinfo.value + ) + + def test_range_with_maxwidth() -> None: c = Circuit(1) a = c.add_c_register("a", 8) @@ -963,6 +1045,60 @@ def test_range_with_maxwidth() -> None: assert "if(a>=1) x q[0];" in qasm +def test_range_predicates_with_non_scratch_bits() -> None: + # https://github.com/CQCL/tket/issues/1530 + range_predicate = RangePredicateOp(3, 0, 5) + c = Circuit(2) + d = c.add_c_register("d", 3) + e = c.add_c_register("e", 2) + f = c.add_c_register("f", 3) + c.add_gate(range_predicate, [d[0], d[1], d[2], e[0]]) + c.add_gate(range_predicate, [f[0], f[1], f[2], e[0]]) + c.add_gate(OpType.Measure, [Qubit(0), f[0]], condition=e[0]) + c.add_gate(OpType.Measure, [Qubit(1), f[1]], condition=if_not_bit(e[0])) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +qreg q[2]; +creg d[3]; +creg e[2]; +creg f[3]; +if(d<=5) e[0] = 1; +if(d>5) e[0] = 0; +if(f<=5) e[0] = 1; +if(f>5) e[0] = 0; +if(e[0]==1) measure q[0] -> f[0]; +if(e[0]==0) measure q[1] -> f[1]; +""" + ) + c = Circuit(2) + d = c.add_c_register("d", 3) + e = c.add_c_register("e", 2) + f = c.add_c_register("f", 3) + c.add_gate(range_predicate, [d[0], d[1], d[2], e[0]]) + c.add_c_setbits([False], [e[0]], condition=e[0]) + c.add_gate(OpType.Measure, [Qubit(0), f[0]], condition=e[0]) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +qreg q[2]; +creg d[3]; +creg e[2]; +creg f[3]; +if(d<=5) e[0] = 1; +if(d>5) e[0] = 0; +if(e[0]==1) e[0] = 0; +if(e[0]==1) measure q[0] -> f[0]; +""" + ) + + if __name__ == "__main__": test_qasm_correct() test_qasm_qubit() diff --git a/pytket/tests/transform_test.py b/pytket/tests/transform_test.py index e4f99f602b..4edac7b179 100644 --- a/pytket/tests/transform_test.py +++ b/pytket/tests/transform_test.py @@ -209,6 +209,16 @@ def test_DecomposeTK2() -> None: assert c.n_gates_of_type(OpType.CX) == 0 assert c.n_gates_of_type(OpType.ZZMax) == 3 + c = Circuit(2).add_gate(OpType.TK2, [0.5, 0.5, 0.5], [0, 1]) + Transform.DecomposeTK2(False, ZZPhase_fidelity=0.8).apply(c) + assert c.n_gates_of_type(OpType.CX) == 0 + assert c.n_gates_of_type(OpType.ZZPhase) == 3 + + c = Circuit(2).add_gate(OpType.TK2, [0.5, 0.5, 0.5], [0, 1]) + Transform.DecomposeTK2(False, ZZPhase_fidelity=lambda _: 0.8).apply(c) + assert c.n_gates_of_type(OpType.CX) == 0 + assert c.n_gates_of_type(OpType.ZZPhase) == 3 + def test_fidelity_KAK() -> None: c = get_KAK_test_circuit() diff --git a/pytket/tests/unit_id/copy_test.py b/pytket/tests/unit_id/copy_test.py new file mode 100644 index 0000000000..fa44a96411 --- /dev/null +++ b/pytket/tests/unit_id/copy_test.py @@ -0,0 +1,49 @@ +# Copyright 2019-2024 Cambridge Quantum Computing +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from copy import copy, deepcopy + +from pytket.unit_id import Bit, Qubit, Node + + +def test_copying_qubits() -> None: + q = Qubit(0) + + q1 = copy(q) + q2 = deepcopy(q) + + assert type(q) is Qubit + assert type(q1) is Qubit + assert type(q2) is Qubit + + +def test_copying_bits() -> None: + b = Bit(0) + + b1 = copy(b) + b2 = deepcopy(b) + + assert type(b) is Bit + assert type(b1) is Bit + assert type(b2) is Bit + + +def test_copying_nodes() -> None: + n = Node(0) + + n1 = copy(n) + n2 = deepcopy(n) + + assert type(n) is Node + assert type(n1) is Node + assert type(n2) is Node diff --git a/recipes/pybind11/conanfile.py b/recipes/pybind11/conanfile.py index 3484785159..7c8b141425 100644 --- a/recipes/pybind11/conanfile.py +++ b/recipes/pybind11/conanfile.py @@ -21,7 +21,7 @@ class PyBind11Conan(ConanFile): name = "pybind11" - version = "2.13.1" + version = "2.13.5" description = "Seamless operability between C++11 and Python" topics = "pybind11", "python", "binding" homepage = "https://github.com/pybind/pybind11" diff --git a/recipes/pybind11_json/all/conanfile.py b/recipes/pybind11_json/all/conanfile.py index 074287c71f..c594ba8c49 100644 --- a/recipes/pybind11_json/all/conanfile.py +++ b/recipes/pybind11_json/all/conanfile.py @@ -29,7 +29,7 @@ def package_id(self): def requirements(self): self.requires("nlohmann_json/3.11.3") - self.requires("pybind11/2.13.1") + self.requires("pybind11/2.13.5") def source(self): get( diff --git a/schemas/compiler_pass_v1.json b/schemas/compiler_pass_v1.json index 3d117a7b1c..e3a807dde3 100644 --- a/schemas/compiler_pass_v1.json +++ b/schemas/compiler_pass_v1.json @@ -320,6 +320,11 @@ "type": ["number", "null"], "minimum": 0, "maximum": 1 + }, + "ZZPhase": { + "type": ["number", "null"], + "minimum": 0, + "maximum": 1 } } }, diff --git a/tket/conanfile.py b/tket/conanfile.py index 7c4ed09f49..a812fd04bb 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.3.17" + version = "1.3.23" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" @@ -111,17 +111,17 @@ def package_info(self): def requirements(self): # libraries installed from remote: # https://quantinuumsw.jfrog.io/artifactory/api/conan/tket1-libs - self.requires("boost/1.85.0", transitive_headers=True) + self.requires("boost/1.86.0", transitive_headers=True) self.requires("symengine/0.12.0", transitive_headers=True) self.requires("eigen/3.4.0", transitive_headers=True) self.requires("nlohmann_json/3.11.3", transitive_headers=True) self.requires("tklog/0.3.3@tket/stable") self.requires("tkassert/0.3.4@tket/stable", transitive_headers=True) self.requires("tkrng/0.3.3@tket/stable") - self.requires("tktokenswap/0.3.8@tket/stable") - self.requires("tkwsm/0.3.8@tket/stable") + self.requires("tktokenswap/0.3.9@tket/stable") + self.requires("tkwsm/0.3.9@tket/stable") if self.build_test(): - self.test_requires("catch2/3.6.0") + self.test_requires("catch2/3.7.0") if self.build_proptest(): self.test_requires("rapidcheck/cci.20230815") diff --git a/tket/include/tket/Ops/ClassicalOps.hpp b/tket/include/tket/Ops/ClassicalOps.hpp index cdb39bcf7e..af92c8332e 100644 --- a/tket/include/tket/Ops/ClassicalOps.hpp +++ b/tket/include/tket/Ops/ClassicalOps.hpp @@ -22,6 +22,7 @@ #include #include "Op.hpp" +#include "OpPtr.hpp" #include "tket/Utils/Json.hpp" namespace tket { @@ -47,11 +48,6 @@ class ClassicalOp : public Op { OpType type, unsigned n_i, unsigned n_io, unsigned n_o, const std::string &name = ""); - // Trivial overrides - Op_ptr symbol_substitution( - const SymEngine::map_basic_basic &) const override { - return std::make_shared(*this); - } SymSet free_symbols() const override { return {}; } unsigned n_qubits() const override { return 0; } @@ -141,6 +137,11 @@ class ClassicalTransformOp : public ClassicalEvalOp { unsigned n, const std::vector &values, const std::string &name = "ClassicalTransform"); + Op_ptr symbol_substitution( + const SymEngine::map_basic_basic &) const override { + return std::make_shared(*this); + } + std::vector eval(const std::vector &x) const override; std::vector get_values() const { return values_; } @@ -171,6 +172,11 @@ class WASMOp : public ClassicalOp { std::vector _width_o_parameter, const std::string &_func_name, const std::string &_wasm_uid); + Op_ptr symbol_substitution( + const SymEngine::map_basic_basic &) const override { + return std::make_shared(*this); + } + /** * return if the op is external */ @@ -284,6 +290,11 @@ class SetBitsOp : public ClassicalEvalOp { : ClassicalEvalOp(OpType::SetBits, 0, 0, values.size(), "SetBits"), values_(values) {} + Op_ptr symbol_substitution( + const SymEngine::map_basic_basic &) const override { + return std::make_shared(*this); + } + std::string get_name(bool latex) const override; std::vector get_values() const { return values_; } @@ -304,6 +315,11 @@ class CopyBitsOp : public ClassicalEvalOp { explicit CopyBitsOp(unsigned n) : ClassicalEvalOp(OpType::CopyBits, n, 0, n, "CopyBits") {} + Op_ptr symbol_substitution( + const SymEngine::map_basic_basic &) const override { + return std::make_shared(*this); + } + std::vector eval(const std::vector &x) const override; }; @@ -345,6 +361,11 @@ class RangePredicateOp : public PredicateOp { uint64_t b = std::numeric_limits::max()) : PredicateOp(OpType::RangePredicate, n, "RangePredicate"), a(a), b(b) {} + Op_ptr symbol_substitution( + const SymEngine::map_basic_basic &) const override { + return std::make_shared(*this); + } + std::string get_name(bool latex) const override; uint64_t upper() const { return b; } @@ -384,6 +405,11 @@ class ExplicitPredicateOp : public PredicateOp { unsigned n, const std::vector &values, const std::string &name = "ExplicitPredicate"); + Op_ptr symbol_substitution( + const SymEngine::map_basic_basic &) const override { + return std::make_shared(*this); + } + std::vector eval(const std::vector &x) const override; std::vector get_values() const { return values_; } @@ -430,6 +456,11 @@ class ExplicitModifierOp : public ModifyingOp { unsigned n, const std::vector &values, const std::string &name = "ExplicitModifier"); + Op_ptr symbol_substitution( + const SymEngine::map_basic_basic &) const override { + return std::make_shared(*this); + } + std::vector eval(const std::vector &x) const override; std::vector get_values() const { return values_; } @@ -448,6 +479,11 @@ class MultiBitOp : public ClassicalEvalOp { public: MultiBitOp(std::shared_ptr op, unsigned n); + Op_ptr symbol_substitution( + const SymEngine::map_basic_basic &) const override { + return std::make_shared(*this); + } + std::string get_name(bool latex) const override; std::shared_ptr get_op() const { return op_; } diff --git a/tket/include/tket/Transformations/Transform.hpp b/tket/include/tket/Transformations/Transform.hpp index 6b0a93cb89..f19f9560da 100644 --- a/tket/include/tket/Transformations/Transform.hpp +++ b/tket/include/tket/Transformations/Transform.hpp @@ -99,7 +99,8 @@ inline const Transform id = struct TwoQbFidelities { std::optional CX_fidelity; std::optional ZZMax_fidelity; - std::optional> ZZPhase_fidelity; + std::optional>> + ZZPhase_fidelity; }; } // namespace Transforms diff --git a/tket/include/tket/Utils/Expression.hpp b/tket/include/tket/Utils/Expression.hpp index a70ca14add..00efb94f85 100644 --- a/tket/include/tket/Utils/Expression.hpp +++ b/tket/include/tket/Utils/Expression.hpp @@ -28,6 +28,15 @@ #include "Json.hpp" #include "Symbols.hpp" +/** Helper struct for use with std::visit */ +template +struct overloaded : Ts... { + using Ts::operator()...; +}; +// explicit deduction guide (not needed as of C++20) +template +overloaded(Ts...) -> overloaded; + namespace tket { /** Representation of a phase as a multiple of \f$ \pi \f$ */ diff --git a/tket/src/Predicates/CompilerPass.cpp b/tket/src/Predicates/CompilerPass.cpp index e9cb2f52ab..56f7054b54 100644 --- a/tket/src/Predicates/CompilerPass.cpp +++ b/tket/src/Predicates/CompilerPass.cpp @@ -15,6 +15,7 @@ #include "tket/Predicates/CompilerPass.hpp" #include +#include #include #include "tket/Mapping/RoutingMethodJson.hpp" @@ -393,7 +394,9 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { content.at("fidelities").at("CX").get>(); fid.ZZMax_fidelity = content.at("fidelities").at("ZZMax").get>(); - fid.ZZPhase_fidelity = std::nullopt; + fid.ZZPhase_fidelity = + content.at("fidelities") + .value>("ZZPhase", std::nullopt); bool allow_swaps = content.at("allow_swaps").get(); pp = DecomposeTK2(fid, allow_swaps); } else if (passname == "PeepholeOptimise2Q") { diff --git a/tket/src/Predicates/PassGenerators.cpp b/tket/src/Predicates/PassGenerators.cpp index 0a6c3f316a..584cb6b062 100644 --- a/tket/src/Predicates/PassGenerators.cpp +++ b/tket/src/Predicates/PassGenerators.cpp @@ -811,7 +811,18 @@ PassPtr DecomposeTK2(const Transforms::TwoQbFidelities& fid, bool allow_swaps) { j["allow_swaps"] = allow_swaps; nlohmann::json fid_json; fid_json["CX"] = fid.CX_fidelity; - fid_json["ZZPhase"] = "SERIALIZATION OF FUNCTIONS IS NOT SUPPORTED"; + if (fid.ZZPhase_fidelity.has_value()) { + std::visit( + overloaded{ + [&fid_json](double arg) { fid_json["ZZPhase"] = arg; }, + [&fid_json](std::function) { + fid_json["ZZPhase"] = + "SERIALIZATION OF FUNCTIONS IS NOT SUPPORTED"; + }}, + fid.ZZPhase_fidelity.value()); + } else { + fid_json["ZZPhase"] = std::nullptr_t{}; + } fid_json["ZZMax"] = fid.ZZMax_fidelity; j["fidelities"] = fid_json; return std::make_shared(precons, t, postcons, j); diff --git a/tket/src/Transformations/Decomposition.cpp b/tket/src/Transformations/Decomposition.cpp index 56b9fe84dc..e3d36157ff 100644 --- a/tket/src/Transformations/Decomposition.cpp +++ b/tket/src/Transformations/Decomposition.cpp @@ -616,7 +616,14 @@ static double best_noise_aware_decomposition( unsigned max_nzz = fid.ZZMax_fidelity ? 1 : 3; for (unsigned n_zz = 0; n_zz <= max_nzz; ++n_zz) { if (n_zz > 0) { - double gate_fid = (*fid.ZZPhase_fidelity)(angles[n_zz - 1]); + double gate_fid = std::visit( + overloaded{// Constant value + [](double arg) { return arg; }, + // A value depending on the angle + [angles, n_zz](std::function arg) { + return (arg)(angles[n_zz - 1]); + }}, + *fid.ZZPhase_fidelity); if (gate_fid < 0 || gate_fid > 1) { throw std::domain_error( "ZZPhase_fidelity returned a value outside of [0, 1]."); @@ -857,7 +864,13 @@ Transform decompose_TK2(const TwoQbFidelities &fid, bool allow_swaps) { } } if (fid.ZZMax_fidelity && fid.ZZPhase_fidelity) { - if (*fid.ZZMax_fidelity < (*fid.ZZPhase_fidelity)(.5)) { + double ZZPhase_half = std::visit( + overloaded{// A constant value. + [](double arg) { return arg; }, + // A value depending on the input. + [](std::function arg) { return (arg)(.5); }}, + *fid.ZZPhase_fidelity); + if (*fid.ZZMax_fidelity < ZZPhase_half) { throw std::domain_error( "The ZZMax fidelity cannot be smaller than the ZZPhase(0.5) " "fidelity"); diff --git a/tket/test/src/Circuit/test_Symbolic.cpp b/tket/test/src/Circuit/test_Symbolic.cpp index 2389786d6e..2445ed219e 100644 --- a/tket/test/src/Circuit/test_Symbolic.cpp +++ b/tket/test/src/Circuit/test_Symbolic.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -259,5 +260,28 @@ SCENARIO("Symbolic GPI, GPI2, AAMS") { } } +SCENARIO("Symbolic substitution for classical operations") { + std::vector and_table = {0, 1, 2, 7, 0, 1, 2, 7}; + std::shared_ptr and_ttop = + std::make_shared(3, and_table); + std::shared_ptr rpop = + std::make_shared(3, 2, 6); + Circuit circ(1, 4); + circ.add_op(OpType::H, {0}); + circ.add_op(and_ttop, {0, 1, 2}); + circ.add_op(and_ttop, {1, 2, 3}); + circ.add_op(rpop, {0, 1, 2, 3}); + circ.add_op(AndOp(), {2, 3, 0}); + circ.add_op(OrOp(), {0, 1, 2}); + circ.add_op(NotOp(), {2, 3}); + circ.add_op(ClassicalX(), {1}); + circ.add_op(ClassicalCX(), {0, 1}); + circ.add_op(AndWithOp(), {2, 3}); + Circuit circ1(circ); + symbol_map_t symmap; + circ1.symbol_substitution(symmap); + REQUIRE(circ == circ1); +} + } // namespace test_Symbolic } // namespace tket diff --git a/tket/test/src/Placement/test_NoiseAwarePlacement.cpp b/tket/test/src/Placement/test_NoiseAwarePlacement.cpp index 9bec2dd4af..df8d720a6f 100644 --- a/tket/test/src/Placement/test_NoiseAwarePlacement.cpp +++ b/tket/test/src/Placement/test_NoiseAwarePlacement.cpp @@ -352,7 +352,9 @@ SCENARIO("Base NoiseAwarePlacement class") { circuit.add_op(OpType::CX, {4, 3}); circuit.add_op(OpType::CX, {0, 5}); - NoiseAwarePlacement placement(architecture); + NoiseAwarePlacement placement( + architecture, std::nullopt, std::nullopt, std::nullopt, 2000, + 1000 /*increase default timeout*/); // note we allow for more matches than should be returned // as noise aware placement returns equal best weighted results // as it does additional cost with device characteristics diff --git a/tket/test/src/test_json.cpp b/tket/test/src/test_json.cpp index 9219258c7f..4af2183bca 100644 --- a/tket/test/src/test_json.cpp +++ b/tket/test/src/test_json.cpp @@ -940,6 +940,7 @@ SCENARIO("Test compiler pass serializations") { COMPPASSJSONTEST(KAKDecomposition, KAKDecomposition(OpType::CX, 0.98)) COMPPASSJSONTEST( DecomposeTK2, DecomposeTK2({0.98, std::nullopt, std::nullopt}, false)) + COMPPASSJSONTEST(DecomposeTK2, DecomposeTK2({0.98, 0.98, 0.98})) COMPPASSJSONTEST(ThreeQubitSquash, ThreeQubitSquash(false)) COMPPASSJSONTEST( EulerAngleReduction, gen_euler_pass(OpType::Rx, OpType::Ry, false)) From 0de35cbaa43ed164aa879de318ff7a658797fc9f Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Fri, 13 Sep 2024 15:53:11 +0100 Subject: [PATCH 08/15] docs: cherry-pick fixes to links and submodule version (#1577) * fix: broken crosslinks to user guide (#1571) * fix: broken crosslinks to user guide * Update README.md Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> * fix creating backends link --------- Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> * docs: use latest version of theming submodule (#1574) * bump submodule * Revert "bump submodule" This reverts commit 063de1de90c32974d1ceb238bdcaa0f181971c34. --------- Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- README.md | 4 +--- pytket/docs/backends.rst | 4 ++-- pytket/docs/circuit_class.rst | 4 ++-- pytket/docs/classical.rst | 2 +- pytket/docs/faqs.rst | 4 ++-- pytket/docs/getting_started.rst | 4 ++-- pytket/docs/index.rst | 2 +- pytket/docs/passes.rst | 2 +- pytket/docs/placement.rst | 2 +- pytket/docs/predicates.rst | 2 +- pytket/docs/qasm.rst | 2 +- pytket/docs/quantinuum-sphinx | 2 +- pytket/package.md | 4 +--- 13 files changed, 17 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 07f63fc3e8..1bb5037a4a 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,8 @@ patchy) is available The `pytket` (Python) API documentation is available [here](https://tket.quantinuum.com/api-docs). -For getting started using pytket, check out the user manual and notebook examples. +For getting started using pytket, check out the [user manual and notebook examples](https://tket.quantinuum.com/user-guide/). -- User manual - https://tket.quantinuum.com/user-manual/ -- Notebook examples - https://tket.quantinuum.com/examples/ The source content for the manual and notebook examples can be found in the [pytket-docs repository](https://github.com/CQCL/pytket-docs). diff --git a/pytket/docs/backends.rst b/pytket/docs/backends.rst index 758ac49a5b..b40a728788 100644 --- a/pytket/docs/backends.rst +++ b/pytket/docs/backends.rst @@ -3,11 +3,11 @@ pytket.backends Contains :py:class:`Backend` abstract class and associated methods. In pytket a :py:class:`Backend` represents an interface between pytket and a quantum device or simulator. Different backends are defined in the various pytket extension modules and inherit from the core pytket :py:class:`Backend` class. -There are several `example notebooks `_ on pytket :py:class:`Backend`\s. If you are interested in developing your own :py:class:`Backend` or pytket extension then see the `creating backends `_ tutorial. +There are several `example notebooks `_ on pytket :py:class:`Backend`\s. If you are interested in developing your own :py:class:`Backend` or pytket extension then see the `creating backends `_ tutorial. Notebook tutorials specific to the :py:class:`QuantinuumBackend` can be found `here `_. -See also the `Running on backends `_ section of the pytket user manual. +See also the `Running on backends `_ section of the pytket user manual. .. automodule:: pytket.backends :members: backend diff --git a/pytket/docs/circuit_class.rst b/pytket/docs/circuit_class.rst index 1340b34c16..a36fe4f7d3 100644 --- a/pytket/docs/circuit_class.rst +++ b/pytket/docs/circuit_class.rst @@ -2,9 +2,9 @@ pytket.circuit.Circuit ====================== :py:class:`Circuit` objects provide an abstraction of quantum circuits. They consist of a set of qubits/quantum wires and a collection of operations applied to them in a given order. These wires have open inputs and outputs, rather than assuming any fixed input state. -See the `pytket User Manual `_ for a step-by-step tutorial on constructing circuits. +See the `pytket User Manual `_ for a step-by-step tutorial on constructing circuits. -See also the notebook tutorials on `circuit generation `_ and `circuit analysis `_. +See also the notebook tutorials on `circuit generation `_ and `circuit analysis `_. Many of the :py:class:`Circuit` methods described below append a gate or box to diff --git a/pytket/docs/classical.rst b/pytket/docs/classical.rst index 9c67bf7c7b..352e9fc747 100644 --- a/pytket/docs/classical.rst +++ b/pytket/docs/classical.rst @@ -1,7 +1,7 @@ pytket.circuit.logic_exp ======================== -For more discussion of classical logic in pytket see the `manual section `_. +For more discussion of classical logic in pytket see the `manual section `_. .. automodule:: pytket.circuit.logic_exp :members: \ No newline at end of file diff --git a/pytket/docs/faqs.rst b/pytket/docs/faqs.rst index 61dddf4676..2a5905ff13 100644 --- a/pytket/docs/faqs.rst +++ b/pytket/docs/faqs.rst @@ -14,7 +14,7 @@ There are two types of rebase 2) :py:class:`RebaseCustom` - This can be used instead of `auto_rebase_pass` in cases where there is no hardcoded conversion available. In this case the user will have to specify how to implement TKET's {TK1, CX} or {TK1, TK2} operations in terms of the target :py:class:`OpType` s. -See the manual section on `rebases `_ for examples. +See the manual section on `rebases `_ for examples. Unitary Synthesis ----------------- @@ -22,7 +22,7 @@ Q: Can TKET generate a circuit to implement a unitary operator of my choice? A: Yes but only up to three qubits at present. This can be done with :py:class:`Unitary3qBox`. -See the manual section on `unitary synthesis `_ . +See the manual section on `unitary synthesis `_ . Qiskit to TKET Conversion diff --git a/pytket/docs/getting_started.rst b/pytket/docs/getting_started.rst index 8d0e47b73a..dcb09e8686 100644 --- a/pytket/docs/getting_started.rst +++ b/pytket/docs/getting_started.rst @@ -67,7 +67,7 @@ Or, if an extension module like ``pytket-qiskit`` is installed: c = qiskit_to_tk(qc) See the -`pytket user manual `_ +`pytket user guide `_ for an extensive tutorial on pytket, providing a gentle introduction to its features and how to run circuits on backend devices, with worked examples. @@ -109,4 +109,4 @@ The following code snippet will show how to compile a circuit to run on an IBM d Here the default compilation pass is applied by :py:meth:`IBMQBackend.get_compiled_circuit`. See `this page `_ for more details. As an alternative, We can experiment with constructing our own circuit compilation routines in pytket. Passes from the :py:mod:`pytket.passes` module can be applied individually or composed in sequence. -See the section of the user manual on `circuit compilation `_ and the corresponding `notebook example `_ for more. +See the section of the user manual on `circuit compilation `_ and the corresponding `notebook example `_ for more. diff --git a/pytket/docs/index.rst b/pytket/docs/index.rst index 4d1ad7b6d6..c051af2248 100644 --- a/pytket/docs/index.rst +++ b/pytket/docs/index.rst @@ -29,7 +29,7 @@ If you have issues installing ``pytket`` please visit the `installation troubles To use ``pytket``, you can simply import the appropriate modules into your python code or in an interactive Python notebook. We can build circuits directly using the ``pytket`` interface by creating a blank circuit and adding gates in the order we want to apply them. See the `Getting Started`_ page for a basic tutorial on using -``pytket``. To get more in depth on features, see the `examples`_. See the `pytket user manual `_ for an extensive introduction to ``pytket`` functionality and how to use it. +``pytket``. To get more in depth on features, see the `examples`_. See the `pytket user guide `_ for an extensive introduction to ``pytket`` functionality and how to use it. Extensions ~~~~~~~~~~ diff --git a/pytket/docs/passes.rst b/pytket/docs/passes.rst index 73c95e6411..b63756c4e1 100644 --- a/pytket/docs/passes.rst +++ b/pytket/docs/passes.rst @@ -9,7 +9,7 @@ Also there are special purpose passes such as `OptimisePhaseGadgets `_ and `auto_rebase_pass `_. -For more on pytket passes see the `compilation `_ section of the user manual or the `notebook tutorials `_ +For more on pytket passes see the `compilation `_ section of the user manual or the `notebook tutorials `_ .. automodule:: pytket._tket.passes diff --git a/pytket/docs/placement.rst b/pytket/docs/placement.rst index 0b97c00e61..8038cc293d 100644 --- a/pytket/docs/placement.rst +++ b/pytket/docs/placement.rst @@ -3,7 +3,7 @@ pytket.placement In order for the constraints of a :py:class:`Backend` to be solved we must first assign device qubits to device-independent (or program) qubits. This module contains three placement methods to perform such an assignment. -For more on qubit placement (and routing in general) see the `qubit mapping and routing `_ tutorial and the corresponding entry in the `user manual `_. +For more on qubit placement (and routing in general) see the `qubit mapping and routing `_ tutorial and the corresponding entry in the `user manual `_. .. automodule:: pytket._tket.placement :members: diff --git a/pytket/docs/predicates.rst b/pytket/docs/predicates.rst index 9de092ea91..7402373c59 100644 --- a/pytket/docs/predicates.rst +++ b/pytket/docs/predicates.rst @@ -3,7 +3,7 @@ pytket.predicates In pytket, predicates enforce properties of circuits. Each pytket :py:class:`Backend` has its own set of predicates which must be satisfied before a quantum circuit can be executed. There are predicates that enforce restrictions including gateset, number of qubits and classical control. -For more on predicates read the corresponding section of the `user manual `_. See also the `Compilation example `_ notebook. +For more on predicates read the corresponding section of the `user manual `_. See also the `Compilation example `_ notebook. .. automodule:: pytket._tket.predicates :members: diff --git a/pytket/docs/qasm.rst b/pytket/docs/qasm.rst index bfd2123800..80d3f76db9 100644 --- a/pytket/docs/qasm.rst +++ b/pytket/docs/qasm.rst @@ -16,7 +16,7 @@ We can set the ``header`` argument in the qasm conversion functions as follows. qasm_str = circuit_to_qasm_str(circ, header="hqslib1") -.. note:: Unlike pytket backends, the qasm converters do not handle `implicit qubit permutations `_. In other words if a circuit containing an implicit qubit permutation is converted to a qasm file the implicit permutation will not be accounted for and the circuit will be missing this permutation when reimported. +.. note:: Unlike pytket backends, the qasm converters do not handle `implicit qubit permutations `_. In other words if a circuit containing an implicit qubit permutation is converted to a qasm file the implicit permutation will not be accounted for and the circuit will be missing this permutation when reimported. .. automodule:: pytket.qasm :members: circuit_from_qasm, circuit_from_qasm_wasm, circuit_to_qasm, circuit_from_qasm_str, circuit_to_qasm_str, circuit_from_qasm_io, circuit_to_qasm_io diff --git a/pytket/docs/quantinuum-sphinx b/pytket/docs/quantinuum-sphinx index 33287684e5..d13546e9de 160000 --- a/pytket/docs/quantinuum-sphinx +++ b/pytket/docs/quantinuum-sphinx @@ -1 +1 @@ -Subproject commit 33287684e58d2b38ab5867c8e79bcd43460676df +Subproject commit d13546e9deecc863e467f15225d79abb08eb6e6e diff --git a/pytket/package.md b/pytket/package.md index 1d253af59c..8a01c74bc6 100644 --- a/pytket/package.md +++ b/pytket/package.md @@ -27,9 +27,7 @@ official Python distribution instead. API reference: https://tket.quantinuum.com/api-docs/ -To get started using pytket see the [user manual](https://tket.quantinuum.com/user-manual/). - -For worked examples using TKET see our [notebook examples](https://tket.quantinuum.com/examples). +To get started using pytket see the [user guide](https://tket.quantinuum.com/user-guide/). ## Support and Discussion From 3166edf8da897af767783080f9196a3dc23dc4f2 Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Wed, 18 Sep 2024 11:05:03 +0100 Subject: [PATCH 09/15] Update quantinuum sphinx. (#1589) Co-authored-by: aidanCQ --- pytket/docs/quantinuum-sphinx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytket/docs/quantinuum-sphinx b/pytket/docs/quantinuum-sphinx index d13546e9de..a8ac27e089 160000 --- a/pytket/docs/quantinuum-sphinx +++ b/pytket/docs/quantinuum-sphinx @@ -1 +1 @@ -Subproject commit d13546e9deecc863e467f15225d79abb08eb6e6e +Subproject commit a8ac27e089824099708da2cea9cef262e4df3ad2 From 4c2cc3e877c9d1bbe8eff5094af3790996695d72 Mon Sep 17 00:00:00 2001 From: Melf Date: Wed, 2 Oct 2024 11:31:48 +0100 Subject: [PATCH 10/15] fix merge --- tket/include/tket/Utils/Expression.hpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tket/include/tket/Utils/Expression.hpp b/tket/include/tket/Utils/Expression.hpp index 7abfdc8c34..2e2a7cd392 100644 --- a/tket/include/tket/Utils/Expression.hpp +++ b/tket/include/tket/Utils/Expression.hpp @@ -42,15 +42,6 @@ struct overloaded : Ts... { template overloaded(Ts...) -> overloaded; -/** Helper struct for use with std::visit */ -template -struct overloaded : Ts... { - using Ts::operator()...; -}; -// explicit deduction guide (not needed as of C++20) -template -overloaded(Ts...) -> overloaded; - namespace tket { /** Representation of a phase as a multiple of \f$ \pi \f$ */ From 5b44430aca9e18dc4806b61142e968f997921e82 Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:39:27 +0100 Subject: [PATCH 11/15] resolve conflicts (#1623) --- .github/workflows/build_and_test.yml | 9 +- .gitignore | 2 + .gitmodules | 8 +- README.md | 6 +- flake.nix | 2 +- nix-support/pytket.nix | 15 +- pytket/docs/_static/nav-config.js | 41 -- pytket/docs/backends.rst | 8 +- pytket/docs/build-docs.sh | 10 + pytket/docs/circuit.rst | 2 +- pytket/docs/circuit_class.rst | 8 +- pytket/docs/classical.rst | 2 +- pytket/docs/conf.py | 211 ------- pytket/docs/display.md | 68 +++ pytket/docs/display.rst | 58 -- pytket/docs/extensions.md | 131 +++++ pytket/docs/extensions.rst | 150 ----- pytket/docs/faqs.rst | 8 +- pytket/docs/getting_started.rst | 16 +- pytket/docs/index.rst | 16 +- pytket/docs/passes.rst | 10 +- pytket/docs/placement.rst | 2 +- pytket/docs/poetry.lock | 817 +++++++++++++-------------- pytket/docs/predicates.rst | 2 +- pytket/docs/pyproject.toml | 7 +- pytket/docs/pytket-docs-theming | 1 + pytket/docs/qasm.rst | 2 +- pytket/docs/quantinuum-sphinx | 1 - pytket/package.md | 8 +- 29 files changed, 671 insertions(+), 950 deletions(-) delete mode 100644 pytket/docs/_static/nav-config.js create mode 100644 pytket/docs/build-docs.sh delete mode 100644 pytket/docs/conf.py create mode 100644 pytket/docs/display.md delete mode 100644 pytket/docs/display.rst create mode 100644 pytket/docs/extensions.md delete mode 100644 pytket/docs/extensions.rst create mode 160000 pytket/docs/pytket-docs-theming delete mode 160000 pytket/docs/quantinuum-sphinx diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 7c72c0483c..7db90a3cce 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -312,18 +312,17 @@ jobs: if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' timeout-minutes: 20 run: | - pip install poetry==1.6.1 + pip install poetry>=1.8.1 + poetry config virtualenvs.create false cd pytket/docs poetry install - cd ../../ - poetry run -C pytket/docs ./.github/workflows/build-docs - + bash ./build-docs.sh - name: Upload artifact if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 with: name: pytket_docs - path: pytket/docs/build/html/ + path: pytket/docs/build/ - name: Upload pytket coverage artefact if: github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index f034b4ca4c..103e4b67f0 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ pytket/tests/qasm_test_files/testout6.qasm result .envrc .direnv/ +*.jupyter_cache +jupyter_execute/ diff --git a/.gitmodules b/.gitmodules index 4ea8925f76..e00d47d2ea 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ -[submodule "pytket/docs/quantinuum-sphinx"] - path = pytket/docs/quantinuum-sphinx - url = https://github.com/CQCL/quantinuum-sphinx.git - branch = dist +[submodule "pytket/docs/pytket-docs-theming"] + path = pytket/docs/pytket-docs-theming + url = https://github.com/CQCL/pytket-docs-theming.git + branch = main diff --git a/README.md b/README.md index 1bb5037a4a..ef9ab37e1a 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,9 @@ patchy) is available [here](https://cqcl.github.io/tket/tket/api/index.html). The `pytket` (Python) API documentation is available -[here](https://tket.quantinuum.com/api-docs). +[here](https://docs.quantinuum.com/tket/api-docs). -For getting started using pytket, check out the [user manual and notebook examples](https://tket.quantinuum.com/user-guide/). +For getting started using pytket, check out the [user manual and notebook examples](https://docs.quantinuum.com/tket/user-guide/). The source content for the manual and notebook examples can be found in the [pytket-docs repository](https://github.com/CQCL/pytket-docs). @@ -39,7 +39,7 @@ The source content for the manual and notebook examples can be found in the [pyt In addition to the core pytket package there are pytket extension modules which allow pytket to interface with quantum devices and simulators. Some extensions also provide interoperability with other software libraries such as qiskit, cirq and pennylane. -For a list of available pytket extensions see the [extensions index page](https://tket.quantinuum.com/api-docs/extensions). +For a list of available pytket extensions see the [extensions index page](https://docs.quantinuum.com/tket/api-docs/extensions). These extensions are installed as separate python packages and the source code for each extension lives in its own github repository. diff --git a/flake.nix b/flake.nix index 44dddc80e0..48722d5c69 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,7 @@ (import ./nix-support/symengine.nix) (import ./nix-support/tket.nix) (import ./nix-support/third-party-python-packages.nix) - (import ./nix-support/pytket.nix) + (import ./nix-support/pytket.nix { package_version = "1.33.0"; }) ]; }; in { diff --git a/nix-support/pytket.nix b/nix-support/pytket.nix index 8dae5ff3dd..37a3abee9e 100644 --- a/nix-support/pytket.nix +++ b/nix-support/pytket.nix @@ -1,13 +1,6 @@ +{ package_version }: self: super: let - config_contents = builtins.readFile ../pytket/docs/conf.py; - versions = - builtins.match ''.*release *= *["']([^"']+)["'].*'' config_contents; - version = if builtins.length versions > 0 then - builtins.elemAt versions 0 - else - builtins.trace "Warning: Unable to find version. Defaulting to 0.0.0" "0.0.0"; - jsonschema-4180 = super.python3Packages.jsonschema.overrideAttrs (_: rec { version = "4.18.0"; src = super.fetchPypi { @@ -36,7 +29,7 @@ in { }; pytket = super.python3.pkgs.buildPythonPackage { pname = "pytket"; - inherit version; + version = package_version; propagatedBuildInputs = with super.python3.pkgs; [ self.binders super.lark @@ -59,13 +52,13 @@ in { # The usual build depends on setuptools-scm to extract the version. # We have already extracted the version within nix, so we can simply # inject it into setup.py. - cat ${../pytket/setup.py} | sed 's/setup(/setup(version="${version}",/' > setup.py; + cat ${../pytket/setup.py} | sed 's/setup(/setup(version="${package_version}",/' > setup.py; mkdir test_root; cp -r ${../pytket/tests} test_root/tests; # hardcode the version extracted from docs/conf.py. chmod 755 pytket - echo '__version__ = "${version}"' > pytket/_version.py; + echo '__version__ = "${package_version}"' > pytket/_version.py; ''; preBuild = '' export USE_NIX=1; diff --git a/pytket/docs/_static/nav-config.js b/pytket/docs/_static/nav-config.js deleted file mode 100644 index b83d723c87..0000000000 --- a/pytket/docs/_static/nav-config.js +++ /dev/null @@ -1,41 +0,0 @@ -const navConfig = { - - "navTextLinks": [ - { - "title": "API Docs", - "href": "../api-docs", - "pathMatch": "somewhere", - }, - { - "title": "Blog", - "href": "../blog/", - "pathMatch": "somewhere", - }, - { - "title": "User Guide", - "href": "../user-guide", - "pathMatch": "somewhere", - }, - ], - "navProductName": "TKET", - "navIconLinks": [ - { - "title": "TKET Github", - "href": "https://github.com/CQCL/tket", - "pathMatch": "somewhere", - "iconImageURL": "_static/assets/github.svg", - }, - { - "title": "TKET Slack Channel", - "href": "https://tketusers.slack.com/", - "pathMatch": "somewhere", - "iconImageURL": "_static/assets/slack.svg", - }, - { - "title": "TKET Stack Exchange", - "href": "https://quantumcomputing.stackexchange.com/questions/tagged/pytket", - "pathMatch": "somewhere", - "iconImageURL": "_static/assets/stack.svg", - }, - ], -} diff --git a/pytket/docs/backends.rst b/pytket/docs/backends.rst index b40a728788..c0b7e39148 100644 --- a/pytket/docs/backends.rst +++ b/pytket/docs/backends.rst @@ -1,13 +1,13 @@ pytket.backends =============== -Contains :py:class:`Backend` abstract class and associated methods. In pytket a :py:class:`Backend` represents an interface between pytket and a quantum device or simulator. Different backends are defined in the various pytket extension modules and inherit from the core pytket :py:class:`Backend` class. +Contains :py:class:`Backend` abstract class and associated methods. In pytket a :py:class:`~pytket.backends.Backend` represents an interface between pytket and a quantum device or simulator. Different backends are defined in the various pytket extension modules and inherit from the core pytket :py:class:`~pytket.backends.Backend` class. -There are several `example notebooks `_ on pytket :py:class:`Backend`\s. If you are interested in developing your own :py:class:`Backend` or pytket extension then see the `creating backends `_ tutorial. +If you are interested in developing your own :py:class:`Backend` or pytket extension then see the `creating backends `_ tutorial. -Notebook tutorials specific to the :py:class:`QuantinuumBackend` can be found `here `_. +Documentation relating to H-Series device access can be found at https://docs.quantinuum.com/h-series/. -See also the `Running on backends `_ section of the pytket user manual. +See also the `Running on backends `_ section of the pytket user manual. .. automodule:: pytket.backends :members: backend diff --git a/pytket/docs/build-docs.sh b/pytket/docs/build-docs.sh new file mode 100644 index 0000000000..a5cb926d21 --- /dev/null +++ b/pytket/docs/build-docs.sh @@ -0,0 +1,10 @@ +cp -R pytket-docs-theming/_static . +cp -R pytket-docs-theming/quantinuum-sphinx . +cp pytket-docs-theming/conf.py . + +sphinx-build -b html . build -D html_title="pytket" + +# Remove copied files. This ensures reusability. +rm -r _static +rm -r quantinuum-sphinx +rm conf.py \ No newline at end of file diff --git a/pytket/docs/circuit.rst b/pytket/docs/circuit.rst index c9e51ac62f..fe50f0d1a0 100644 --- a/pytket/docs/circuit.rst +++ b/pytket/docs/circuit.rst @@ -7,7 +7,7 @@ pytket.circuit circuit_class.rst optype.rst classical.rst - display.rst + display.md .. currentmodule:: pytket._tket.circuit diff --git a/pytket/docs/circuit_class.rst b/pytket/docs/circuit_class.rst index a36fe4f7d3..b9208d16f9 100644 --- a/pytket/docs/circuit_class.rst +++ b/pytket/docs/circuit_class.rst @@ -2,12 +2,12 @@ pytket.circuit.Circuit ====================== :py:class:`Circuit` objects provide an abstraction of quantum circuits. They consist of a set of qubits/quantum wires and a collection of operations applied to them in a given order. These wires have open inputs and outputs, rather than assuming any fixed input state. -See the `pytket User Manual `_ for a step-by-step tutorial on constructing circuits. +See the `pytket User Manual `_ for a step-by-step tutorial on constructing circuits. -See also the notebook tutorials on `circuit generation `_ and `circuit analysis `_. +See also the notebook tutorials on `circuit generation `_ and `circuit analysis `_. -Many of the :py:class:`Circuit` methods described below append a gate or box to +Many of the :py:class:`~pytket.circuit.Circuit` methods described below append a gate or box to the end of the circuit. Where ``kwargs`` are indicated in these methods, the following keyword arguments are supported: @@ -338,7 +338,7 @@ condition on a specified set of bit values.) .. Note:: For adding boxes to a circuit the :py:meth:`Circuit.add_gate` method is sufficient to append any :py:class:`OpType` to a :py:class:`Circuit`. - .. jupyter-input:: + :: from pytket.circuit import Circuit, CircBox diff --git a/pytket/docs/classical.rst b/pytket/docs/classical.rst index 352e9fc747..ca7cd186e1 100644 --- a/pytket/docs/classical.rst +++ b/pytket/docs/classical.rst @@ -1,7 +1,7 @@ pytket.circuit.logic_exp ======================== -For more discussion of classical logic in pytket see the `manual section `_. +For more discussion of classical logic in pytket see the `manual section `_. .. automodule:: pytket.circuit.logic_exp :members: \ No newline at end of file diff --git a/pytket/docs/conf.py b/pytket/docs/conf.py deleted file mode 100644 index 988400b18c..0000000000 --- a/pytket/docs/conf.py +++ /dev/null @@ -1,211 +0,0 @@ -# Copyright 2019-2024 Cambridge Quantum Computing -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -*- coding: utf-8 -*- -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - - -# -- Project information ----------------------------------------------------- - -project = "pytket" -copyright = "2024 Quantinuum" -author = "Quantinuum" - -# The short X.Y version -version = "1.33" -# The full version, including alpha/beta/rc tags -release = "1.33.0" - - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.autosummary", - "sphinx.ext.mathjax", - "sphinx.ext.viewcode", - "sphinx_copybutton", - "jupyter_sphinx", - "enum_tools.autoenum", -] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = ".rst" - -# The master toctree document. -master_doc = "index" - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = "en" - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "furo" -templates_path = ["./quantinuum-sphinx/_templates/"] -html_static_path = ["./quantinuum-sphinx/_static/", "_static/"] -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# - - -exclude_patterns = ["build/jupyter_execute/*", ".venv/*"] - -html_theme_options = {} - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = "pytketdoc" - -epub_exclude_files = ["search.html"] - -autodoc_member_order = "groupwise" - -# The following code is for resolving broken hyperlinks in the doc. - -import re -from typing import Any, Dict, List - -from sphinx.application import Sphinx - -# "matching name" : "correct name" -custom_internal_mapping = { - "pytket.utils.outcomearray.OutcomeArray": "pytket.utils.OutcomeArray", - "pytket.utils.operators.QubitPauliOperator": "pytket.utils.QubitPauliOperator", - "pytket.backends.backend.Backend": "pytket.backends.Backend", - "tket::Predicate": "pytket._tket.predicates.Predicate", - "tket::Qubit": "pytket._tket.circuit.Qubit", - "tket::Architecture": "pytket._tket.architecture.Architecture", - "tket::RoutingMethod": "pytket._tket.mapping.RoutingMethod", - "tket::CircBox": "pytket._tket.circuit.CircBox", - "tket::ExpBox": "pytket._tket.circuit.ExpBox", - "tket::QControlBox": "pytket._tket.circuit.QControlBox", - "tket::Unitary1qBox": "pytket._tket.circuit.Unitary1qBox", - "tket::Unitary2qBox": "pytket._tket.circuit.Unitary2qBox", - "tket::PauliExpBox": "pytket._tket.circuit.PauliExpBox", - "tket::TermSequenceBox": "pytket._tket.circuit.TermSequenceBox", - "tket::ToffoliBox": "pytket._tket.circuit.ToffoliBox", - "tket::OpType": "pytket._tket.circuit.OpType", - "tket::CompilationUnit": "pytket._tket.predicates.CompilationUnit", - "tket::CompositeGateDef": "pytket._tket.circuit.CustomGateDef", - "tket::CompositeGate": "pytket._tket.circuit.CustomGate", - "tket::Pauli": "pytket._tket.circuit.Pauli", -} - - -def correct_docstring( - app: Sphinx, - what: str, - name: str, - obj: Any, - options: Dict, - lines: List[str], -) -> None: - # Replace names in docstrings - for k, v in app.config.custom_internal_mapping.items(): - for i, line in enumerate(lines): - lines[i] = line.replace(k, v) - - -def correct_signature( - app: Sphinx, - what: str, - name: str, - obj: Any, - options: Dict, - signature: str, - return_annotation: str, -) -> (str, str): - # The "autodoc-process-signature" event is emitted when autodoc has formatted a - # signature for an object. This event precedes the creation of the doctree - # (i.e. docutils.document). It's best to correct any wrong types here because some types - # won't be correctly converted to a docutils.node. - # For example, "Dict[tket::Architecture, any]" will be converted to "Dict[tket::Architecture, any]" - # instead of the correct structure "Dict, [, tket::Architecture, any, ]", this breaks all nested references. - - new_signature = signature - new_return_annotation = return_annotation - for k, v in app.config.custom_internal_mapping.items(): - if signature is not None: - new_signature = new_signature.replace(k, v) - if return_annotation is not None: - new_return_annotation = new_return_annotation.replace(k, v) - - # e.g. Replace by CXConfigType.Snake to avoid silent failure in later stages. - if new_signature is not None: - enums_signature = re.findall(r"<.+?\: \d+>", new_signature) - for e in enums_signature: - new_signature = new_signature.replace(e, e[1 : e.find(":")]) - - if new_return_annotation is not None: - enums_return = re.findall(r"<.+?\: \d+>", new_return_annotation) - for e in enums_return: - new_return_annotation = new_return_annotation.replace(e, e[1 : e.find(":")]) - - return new_signature, new_return_annotation - - -def setup(app): - app.add_config_value("custom_internal_mapping", {}, "env") - app.connect("autodoc-process-signature", correct_signature) - app.connect("autodoc-process-docstring", correct_docstring) diff --git a/pytket/docs/display.md b/pytket/docs/display.md new file mode 100644 index 0000000000..e74f075494 --- /dev/null +++ b/pytket/docs/display.md @@ -0,0 +1,68 @@ +--- +file_format: mystnb +kernelspec: + name: python3 +--- +# pytket.circuit.display + +Contains several functions for rendering interactive circuit diagrams. + +```{note} +Rendering circuits with `pytket.circuit.display` requires an internet connection. Using the pytket circuit renderer offline can be done by installing the [pytket-offline-display extension](https://github.com/CQCL/pytket-offline-renderer). +``` + +``` +pip install pytket-offline-display +``` + +```{eval-rst} +.. automodule:: pytket.circuit.display + :members: get_circuit_renderer, CircuitRenderer +``` + +## Example usage: + +```{code-cell} ipython3 + +from pytket import Circuit +from pytket.circuit.display import get_circuit_renderer + +circuit_renderer = get_circuit_renderer() # Instantiate a circuit renderer +circuit_renderer.set_render_options(zx_style=True) # Configure render options +circuit_renderer.condense_c_bits = False # You can also set the properties on the instance directly +print("Render options:") +print(circuit_renderer.get_render_options()) # View currently set render options + +circuit_renderer.min_height = "300px" # Change the display height + +circ = Circuit(2,2) # Define Circuit +circ.H(0).H(1).CX(0, 1).Rz(0.4, 1).CX(0, 1).H(0).H(1).measure_all() + +circuit_renderer.render_circuit_jupyter(circ) # Render interactive display +``` + +You can save the render options to the pytket config via `circuit_renderer.save_render_options()`. +You can then import the render functions directly with your config already applied: + +```{code-cell} ipython3 + +from pytket import Circuit +from pytket.circuit.display import render_circuit_jupyter + +circ = Circuit(2,2) # Define Circuit +circ.H(0).H(1).CX(0, 1).Rz(0.4, 1).CX(0, 1).H(0).H(1).measure_all() + +render_circuit_jupyter(circ) # Render with default/saved options +``` + +This same diagram can be rendered with the offline renderer as follows + +```{code-cell} ipython3 +--- +tags: [skip-execution] +--- +from pytket.extensions.offline_display import get_circuit_renderer, render_circuit_jupyter + +custom_renderer = get_circuit_renderer() +custom_renderer.render_circuit_jupyter(circ) # Render configurable display as above +render_circuit_jupyter(circ) # Render using default options diff --git a/pytket/docs/display.rst b/pytket/docs/display.rst deleted file mode 100644 index 3d0509de39..0000000000 --- a/pytket/docs/display.rst +++ /dev/null @@ -1,58 +0,0 @@ -pytket.circuit.display -================================== - -Contains several functions for rendering interactive circuit diagrams. - -.. note:: Rendering circuits with ``pytket.circuit.display`` requires an internet connection. Using the pytket circuit renderer offline can be done by installing the `pytket-offline-display extension `_. - - :: - - pip install pytket-offline-display - -.. automodule:: pytket.circuit.display - :members: get_circuit_renderer, CircuitRenderer - -Example usage: --------------- - -.. jupyter-execute:: - - from pytket import Circuit - from pytket.circuit.display import get_circuit_renderer - - circuit_renderer = get_circuit_renderer() # Instantiate a circuit renderer - circuit_renderer.set_render_options(zx_style=True) # Configure render options - circuit_renderer.condense_c_bits = False # You can also set the properties on the instance directly - print("Render options:") - print(circuit_renderer.get_render_options()) # View currently set render options - - circuit_renderer.min_height = "300px" # Change the display height - - circ = Circuit(2,2) # Define Circuit - circ.H(0).H(1).CX(0, 1).Rz(0.4, 1).CX(0, 1).H(0).H(1).measure_all() - - circuit_renderer.render_circuit_jupyter(circ) # Render interactive display - -You can save the render options to the pytket config via ``circuit_renderer.save_render_options()``. -You can then import the render functions directly with your config already applied: - -.. jupyter-execute:: - - from pytket import Circuit - from pytket.circuit.display import render_circuit_jupyter - - circ = Circuit(2,2) # Define Circuit - circ.H(0).H(1).CX(0, 1).Rz(0.4, 1).CX(0, 1).H(0).H(1).measure_all() - - render_circuit_jupyter(circ) # Render with default/saved options - -This same diagram can be rendered with the offline renderer as follows - - :: - - from pytket.extensions.offline_display import get_circuit_renderer, render_circuit_jupyter - - custom_renderer = get_circuit_renderer() - custom_renderer.render_circuit_jupyter(circ) # Render configurable display as above - render_circuit_jupyter(circ) # Render using default options - diff --git a/pytket/docs/extensions.md b/pytket/docs/extensions.md new file mode 100644 index 0000000000..73f9535bb2 --- /dev/null +++ b/pytket/docs/extensions.md @@ -0,0 +1,131 @@ +# Extension packages + +The pytket extensions are separate python modules which allow pytket to interface with backends from a range of providers including quantum devices from Quantinuum and IBM. +In pytket a `Backend` represents a connection to a QPU (Quantum Processing Unit) or simulator for processing quantum circuits. One can also access additional quantum devices and simulators via the cloud through the extensions for [Braket](inv:pytket-braket:std:doc#index) . + +Additionally, the extensions allow pytket to cross-compile circuits from different quantum computing libraries with the extensions for [qiskit](inv:pytket-qiskit:std:doc#index), [cirq](inv:pytket-cirq:std:doc#index) and [pennylane](inv:pytket-pennylane:std:doc#index) . This enables pytket's compilation features to be used in conjunction with other software tools. + +The additional modules can be installed adding the extension name to the installation command for pytket. For example pytket-quantinuum can be installed by running + +``` +pip install pytket-quantinuum +``` + +The types of `Backend` available in pytket are the following + +## Types of Backend + +- **QPUs** - These are real quantum computers that return shots based results. E.g the [QuantinuumBackend](inv:#*.QuantinuumBackend). +- **Cloud Access** - Cloud backends allow pytket to interface with cloud platforms to access additional QPUs and simulators. E.g [BraketBackend](inv:#*.BraketBackend). +- **Emulators** - These classically simulate a circuit and produce shots based results. Sometimes emulators use a noise model and have connectivity constraints to emulate real QPUs. E.g. [IBMQEmulatorBackend](inv:#*.IBMQEmulatorBackend) +- **Statevector Simulators** - Calculates the pure quantum state prepared by a circuit returning a vector/ndarray. Examples of statevector simulators are the [ForestStateBackend](inv:#*.ForestStateBackend) and the [AerStateBackend](inv:#*.AerStateBackend). +- **Unitary Simulators** - Unitary simulators calculate the unitary operator that is applied by a circuit. A unitary matrix/ndarray is returned [AerUnitaryBackend](inv:#*.AerUnitaryBackend) is an example of such a simulator. +- **Density Matrix Simulators** - These simulators compute the density matrix prepared by a circuit. The result can be a statistical mixture of states in contrast to statevector simulation. E.g. [CirqDensityMatrixSampleBackend](inv:#*.CirqDensityMatrixSampleBackend) +- **Other specialised simulators** - There are extensions for simulating specific types of circuit. For instance the [SimplexBackend](inv:#*.SimplexBackend) is designed to simulate Clifford circuits. + +A full list of available pytket backends is shown below. + +## QPUs + +[QuantinuumBackend](inv:#*.QuantinuumBackend) +\- Interface to a remote Quantinuum device or simulator. There are currently two Quantinuum devices offered (H1-1 and H2-1). + +[IBMQBackend](inv:#*.IBMQBackend) +\- A backend for running circuits on remote IBMQ devices. + +[ForestBackend](inv:#*.ForestBackend) +\- A backend for running circuits on remote Rigetti devices. + +[AQTBackend](https://cqcl.github.io/pytket-aqt/api/api.html#pytket.extensions.aqt.AQTBackend) +\- Interface to an AQT device or simulator. + +[IQMBackend](inv:#*.IQMBackend) +\- Interface to an IQM device or simulator. + +## Cloud Access + +[BraketBackend](inv:#*.BraketBackend) +\- Interface to Amazon Braket service. + +## Emulators + +[IBMQEmulatorBackend](inv:#*.IBMQEmulatorBackend) - A backend which uses the [AerBackend](inv:#*.AerBackend) to emulate the behavior of IBMQBackend. + +[QuantinuumBackend](inv:#*.QuantinuumBackend) +\- The QuantinuumBackend has two available emulators namely H1-1E and H2-1E. These are device specific emulators for the H1-1 and H2-1 devices. These emulators run remotely on a server. + +## Statevector Simulators + +[CirqStateSampleBackend](inv:#*.CirqStateSampleBackend) +\- Backend for Cirq statevector simulator sampling. + +[CirqStateSimBackend](inv:#*.CirqStateSimBackend) +\- Backend for Cirq statevector simulator state return. + +[AerStateBackend](inv:#*.AerStateBackend) - Backend for running simulations on the Qiskit Aer Statevector simulator. + +[ForestStateBackend](inv:#*.ForestStateBackend) - State-based interface to a Rigetti device. + +[ProjectQBackend](inv:#*.ProjectQBackend) +\- Backend for running statevector simulations on the ProjectQ simulator. + +## Unitary Simulators + +[AerUnitaryBackend](inv:#*.AerUnitaryBackend) - Backend for running simulations on the Qiskit Aer unitary simulator. + +## Density Matrix Simulators + +[CirqDensityMatrixSampleBackend](inv:#*.CirqDensityMatrixSampleBackend) +\- Backend for Cirq density matrix simulator sampling. + +[CirqDensityMatrixSimBackend](inv:#*.CirqDensityMatrixSimBackend) +\- Backend for Cirq density matrix simulator density_matrix return. + +[QulacsBackend](inv:#*.QulacsBackend) - This has a configurable density matrix simulation option. + +Use `QulacsBackend(result_type="density_matrix")`. + +## Clifford Simulators + +[CirqCliffordSampleBackend](inv:#*.CirqCliffordSampleBackend) +\- Backend for Cirq Clifford simulator sampling. + +[CirqCliffordSimBackend](inv:#*.CirqCliffordSimBackend) +\- Backend for Cirq Clifford simulator state return. + +[SimplexBackend](inv:#*.SimplexBackend)- Backend for simulating Clifford circuits using pysimplex. + +[StimBackend](inv:#*.StimBackend) +\- Backend for simulating Clifford circuits using Stim. + +## Other + +[AerBackend](inv:#*.AerBackend) +\- Backend for running simulations on the Qiskit Aer QASM simulator. This simulator is noiseless by default but can take a user defined `NoiseModel`. + +[QulacsBackend](inv:#*.QulacsBackend) +\- Backend for running simulations of variational quantum circuits on the Qulacs simulator. + +```{toctree} +:caption: 'Extensions:' +:maxdepth: 0 + +pytket-aqt +pytket-azure +pytket-braket +pytket-cirq +pytket-iqm +pytket-pennylane +pytket-projectq +pytket-pyquil +pytket-pysimplex +pytket-pyzx +pytket-qir +pytket-qiskit +pytket-quantinuum +pytket-cutensornet +pytket-qulacs +pytket-qujax +pytket-stim +``` + diff --git a/pytket/docs/extensions.rst b/pytket/docs/extensions.rst deleted file mode 100644 index 7dba547c7f..0000000000 --- a/pytket/docs/extensions.rst +++ /dev/null @@ -1,150 +0,0 @@ -Extension packages -================== - -The pytket extensions are separate python modules which allow pytket to interface with backends from a range of providers including quantum devices from Quantinuum and IBM. -In pytket a ``Backend`` represents a connection to a QPU (Quantum Processing Unit) or simulator for processing quantum circuits. One can also access additional quantum devices and simulators via the cloud through the extensions for `Braket `_ . - -Additionally, the extensions allow pytket to cross-compile circuits from different quantum computing libraries with the extensions for `qiskit `_, `cirq `_ and `pennylane `_ . This enables pytket's compilation features to be used in conjunction with other software tools. - -The additional modules can be installed adding the extension name to the installation command for pytket. For example pytket-quantinuum can be installed by running - -:: - - pip install pytket-quantinuum - -The types of ``Backend`` available in pytket are the following - -Types of Backend ----------------- - -* **QPUs** - These are real quantum computers that return shots based results. E.g the `QuantinuumBackend `_ . -* **Cloud Access** - Cloud backends allow pytket to interface with cloud platforms to access additional QPUs and simulators. E.g `BraketBackend `_ . -* **Emulators** - These classically simulate a circuit and produce shots based results. Sometimes emulators use a noise model and have connectivity constraints to emulate real QPUs. E.g. `IBMQEmulatorBackend`_ -* **Statevector Simulators** - Calculates the pure quantum state prepared by a circuit returning a vector/ndarray. Examples of statevector simulators are the `ForestStateBackend`_ and the `AerStateBackend`_. -* **Unitary Simulators** - Unitary simulators calculate the unitary operator that is applied by a circuit. A unitary matrix/ndarray is returned `AerUnitaryBackend`_ is an example of such a simulator. -* **Density Matrix Simulators** - These simulators compute the density matrix prepared by a circuit. The result can be a statistical mixture of states in contrast to statevector simulation. E.g. `CirqDensityMatrixSampleBackend`_ -* **Other specialised simulators** - There are extensions for simulating specific types of circuit. For instance the `SimplexBackend`_ is designed to simulate Clifford circuits. - -A full list of available pytket backends is shown below. - -QPUs ----- - -`QuantinuumBackend `_ -- Interface to a remote Quantinuum device or simulator. There are currently two Quantinuum devices offered (H1-1 and H2-1). - -`IBMQBackend `_ -- A backend for running circuits on remote IBMQ devices. - -`ForestBackend `_ -- A backend for running circuits on remote Rigetti devices. - -`AQTBackend `_ -- Interface to an AQT device or simulator. - -`IQMBackend `_ -- Interface to an IQM device or simulator. - -Cloud Access ------------- - -`BraketBackend `_ -- Interface to Amazon Braket service. - -Emulators ---------- - -`IBMQEmulatorBackend`_ - A backend which uses the `AerBackend `_ to emulate the behavior of IBMQBackend. - -`QuantinuumBackend `_ -- The QuantinuumBackend has two available emulators namely H1-1E and H2-1E. These are device specific emulators for the H1-1 and H2-1 devices. These emulators run remotely on a server. - -Statevector Simulators ----------------------- - -`CirqStateSampleBackend `_ -- Backend for Cirq statevector simulator sampling. - -`CirqStateSimBackend `_ -- Backend for Cirq statevector simulator state return. - -`AerStateBackend`_ - Backend for running simulations on the Qiskit Aer Statevector simulator. - -`ForestStateBackend`_ - State-based interface to a Rigetti device. - -`ProjectQBackend `_ -- Backend for running statevector simulations on the ProjectQ simulator. - -Unitary Simulators ------------------- - -`AerUnitaryBackend`_ - Backend for running simulations on the Qiskit Aer unitary simulator. - -Density Matrix Simulators -------------------------- - -`CirqDensityMatrixSampleBackend`_ -- Backend for Cirq density matrix simulator sampling. - -`CirqDensityMatrixSimBackend `_ -- Backend for Cirq density matrix simulator density_matrix return. - -`QulacsBackend`_ - This has a configurable density matrix simulation option. - -Use ``QulacsBackend(result_type="density_matrix")``. - -Clifford Simulators -------------------- - -`CirqCliffordSampleBackend `_ -- Backend for Cirq Clifford simulator sampling. - -`CirqCliffordSimBackend `_ -- Backend for Cirq Clifford simulator state return. - -`SimplexBackend`_- Backend for simulating Clifford circuits using pysimplex. - -`StimBackend `_ -- Backend for simulating Clifford circuits using Stim. - -Other ------ - -`AerBackend `_ -- Backend for running simulations on the Qiskit Aer QASM simulator. This simulator is noiseless by default but can take a user defined ``NoiseModel``. - -`QulacsBackend `_ -- Backend for running simulations of variational quantum circuits on the Qulacs simulator. - -.. toctree:: - :caption: Extensions: - :maxdepth: 0 - - pytket-aqt - pytket-azure - pytket-braket - pytket-cirq - pytket-iqm - pytket-pennylane - pytket-projectq - pytket-pyquil - pytket-pysimplex - pytket-pyzx - pytket-qir - pytket-qiskit - pytket-quantinuum - pytket-cutensornet - pytket-qulacs - pytket-qujax - pytket-stim - - -.. _pytket: https://tket.quantinuum.com/api-docs -.. _Quantinuum: https://quantinuum.com -.. _IBMQEmulatorBackend: https://tket.quantinuum.com/extensions/pytket-qiskit/api.html#pytket.extensions.qiskit.IBMQEmulatorBackend -.. _AerStateBackend: https://tket.quantinuum.com/extensions/pytket-qiskit/api.html#pytket.extensions.qiskit.AerStateBackend -.. _ForestStateBackend: https://tket.quantinuum.com/extensions/pytket-pyquil/api.html#pytket.extensions.pyquil.ForestStateBackend -.. _AerUnitaryBackend: https://tket.quantinuum.com/extensions/pytket-qiskit/api.html#pytket.extensions.qiskit.AerUnitaryBackend -.. _CirqDensityMatrixSampleBackend: https://tket.quantinuum.com/extensions/pytket-cirq/api.html#pytket.extensions.cirq.CirqDensityMatrixSampleBackend -.. _SimplexBackend: https://tket.quantinuum.com/extensions/pytket-pysimplex/api.html#pytket.extensions.pysimplex.SimplexBackend -.. _QulacsBackend: https://tket.quantinuum.com/extensions/pytket-qulacs/api.html#pytket.extensions.qulacs.QulacsBackend diff --git a/pytket/docs/faqs.rst b/pytket/docs/faqs.rst index 2a5905ff13..d5e333b6f2 100644 --- a/pytket/docs/faqs.rst +++ b/pytket/docs/faqs.rst @@ -1,6 +1,6 @@ TKET FAQs ~~~~~~~~~ -These are frequently asked questions that relate to the use of pytket. For installation FAQs see the `installation troubleshooting `_ page. +These are frequently asked questions that relate to the use of pytket. For installation FAQs see the `installation troubleshooting `_ page. Rebases ------- @@ -14,7 +14,7 @@ There are two types of rebase 2) :py:class:`RebaseCustom` - This can be used instead of `auto_rebase_pass` in cases where there is no hardcoded conversion available. In this case the user will have to specify how to implement TKET's {TK1, CX} or {TK1, TK2} operations in terms of the target :py:class:`OpType` s. -See the manual section on `rebases `_ for examples. +See the manual section on `rebases `_ for examples. Unitary Synthesis ----------------- @@ -22,7 +22,7 @@ Q: Can TKET generate a circuit to implement a unitary operator of my choice? A: Yes but only up to three qubits at present. This can be done with :py:class:`Unitary3qBox`. -See the manual section on `unitary synthesis `_ . +See the manual section on `unitary synthesis `_ . Qiskit to TKET Conversion @@ -30,7 +30,7 @@ Qiskit to TKET Conversion Q: How can I convert my qiskit :py:class:`QuantumCircuit` to a pytket :py:class:`Circuit`? -A: This can be achieved using the :py:meth:`qiskit_to_tk` function from the `pytket-qiskit extension `_ +A: This can be achieved using the :py:meth:`qiskit_to_tk` function from the `pytket-qiskit extension `_ :: diff --git a/pytket/docs/getting_started.rst b/pytket/docs/getting_started.rst index dcb09e8686..e123a904a6 100644 --- a/pytket/docs/getting_started.rst +++ b/pytket/docs/getting_started.rst @@ -21,7 +21,7 @@ those using an older version of pytket, keep up to date by installing with the There are separate packages for managing the interoperability between pytket and other quantum software packages which can also be installed via PyPI. For details of these, see the -`pytket extensions `_ documentation. +`pytket extensions `_ documentation. The quantum circuit is an abstraction of computation using quantum resources, @@ -38,14 +38,14 @@ use them directly. c = Circuit(2, 2) # define a circuit with 2 qubits and 2 bits c.H(0) # add a Hadamard gate to qubit 0 c.Rz(0.25, 0) # add an Rz gate of angle 0.25*pi to qubit 0 - c.CX(1,0) # add a CX gate with control qubit 1 and target qubit 0 + c.CX(1, 0) # add a CX gate with control qubit 1 and target qubit 0 c.measure_all() # measure qubits 0 and 1, recording the results in bits 0 and 1 Pytket provides many handy shortcuts and higher-level components for building circuits, including custom gate definitions, circuit composition, gates with symbolic parameters, and conditional gates. -On the other hand, pytket's flexibile interface allows you to take circuits +On the other hand, pytket's flexible interface allows you to take circuits defined in a number of languages, including raw source code languages such as OpenQASM and Quipper, or embedded python frameworks such as Qiskit and Cirq. @@ -67,12 +67,12 @@ Or, if an extension module like ``pytket-qiskit`` is installed: c = qiskit_to_tk(qc) See the -`pytket user guide `_ +`pytket user guide `_ for an extensive tutorial on pytket, providing a gentle introduction to its features and how to run circuits on backend devices, with worked examples. In pytket there is also a generic :py:class:`Backend` interface. This represents a connection to a quantum device or simulator. -It's possible to run circuits on platforms from different providers through the `extension modules `_. +It's possible to run circuits on platforms from different providers through the `extension modules `_. :: @@ -93,7 +93,7 @@ This prints out a summary of readouts (the final values of the classical bits) a Each pytket :py:class:`Backend` comes with its own default compilation method. This is a recommended sequence of optimisation passes to meet the requirements of the specific :py:class:`Backend`. -The following code snippet will show how to compile a circuit to run on an IBM device. This requires setting up IBM credentials (see the `credentials guide `_). +The following code snippet will show how to compile a circuit to run on an IBM device. This requires setting up IBM credentials (see the `credentials guide `_). :: @@ -106,7 +106,7 @@ The following code snippet will show how to compile a circuit to run on an IBM d compiled_circ = nairobi_device.get_compiled_circuit(circ) result = backend.run_circuit(compiled_circ, n_shots=100) -Here the default compilation pass is applied by :py:meth:`IBMQBackend.get_compiled_circuit`. See `this page `_ for more details. +Here the default compilation pass is applied by :py:meth:`IBMQBackend.get_compiled_circuit`. See `this page `_ for more details. As an alternative, We can experiment with constructing our own circuit compilation routines in pytket. Passes from the :py:mod:`pytket.passes` module can be applied individually or composed in sequence. -See the section of the user manual on `circuit compilation `_ and the corresponding `notebook example `_ for more. +See the section of the user manual on `circuit compilation `_ and the corresponding `notebook example `_ for more. diff --git a/pytket/docs/index.rst b/pytket/docs/index.rst index c051af2248..f8c40521d3 100644 --- a/pytket/docs/index.rst +++ b/pytket/docs/index.rst @@ -2,7 +2,7 @@ pytket ====== ``pytket`` is a python module for interfacing with tket, a quantum computing toolkit and optimising compiler developed by `Quantinuum`_. We currently support circuits and device architectures from -`numerous providers `_, allowing the +`numerous providers `_, allowing the tket tools to be used in conjunction with projects on their platforms. ``pytket`` is available for Python 3.10, 3.11 and 3.12, on Linux, MacOS and @@ -24,12 +24,12 @@ Windows. To install, run `pip install types-pkg-resources==0.1.3 pytket==` -If you have issues installing ``pytket`` please visit the `installation troubleshooting `_ page. +If you have issues installing ``pytket`` please visit the `installation troubleshooting `_ page. To use ``pytket``, you can simply import the appropriate modules into your python code or in an interactive Python notebook. We can build circuits directly using the ``pytket`` interface by creating a blank circuit and adding gates in the order we want to apply them. -See the `Getting Started`_ page for a basic tutorial on using -``pytket``. To get more in depth on features, see the `examples`_. See the `pytket user guide `_ for an extensive introduction to ``pytket`` functionality and how to use it. +See the `Getting Started `_ page for a basic tutorial on using +``pytket``. To get more in depth on features, see the `pytket user guide `_ for an extensive introduction to ``pytket`` functionality and how to use it. Extensions ~~~~~~~~~~ @@ -48,7 +48,7 @@ To install the ``pytket-quantinuum`` package use the following command. pip install pytket-quantinuum The extensions supported by tket are described -`here `_. +`here `_. How to cite ~~~~~~~~~~~ @@ -80,8 +80,6 @@ LICENCE Licensed under the `Apache 2 License `_. -.. _Getting Started: getting_started.html -.. _examples: https://tket.quantinuum.com/examples .. _Quantinuum: https://www.quantinuum.com/ .. toctree:: @@ -95,8 +93,8 @@ Licensed under the `Apache 2 License `_; however passes allow for additional predicate checking and compositionality. +In pytket, compilation passes perform in-place transformations of circuits. From a user's point of view, passes are similar to `transforms `_; however passes allow for additional predicate checking and compositionality. -There are passes such as `FullPeepholeOptimise `_ and `KAKDecomposition `_ which are designed for general purpose circuit optimisation. +There are passes such as `FullPeepholeOptimise `_ and `KAKDecomposition `_ which are designed for general purpose circuit optimisation. -Also there are special purpose passes such as `OptimisePhaseGadgets `_ and `PauliSimp `_ which perform optimisation by targeting phase gadget and Pauli gadget structures within circuits. For more on these optimisation techniques see the `corresponding publication `_. +Also there are special purpose passes such as `OptimisePhaseGadgets `_ and `PauliSimp `_ which perform optimisation by targeting phase gadget and Pauli gadget structures within circuits. For more on these optimisation techniques see the `corresponding publication `_. -Rebase passes can be used to convert a circuit to a desired gateset. See `RebaseCustom `_ and `auto_rebase_pass `_. +Rebase passes can be used to convert a circuit to a desired gateset. See `RebaseCustom `_ and `auto_rebase_pass `_. -For more on pytket passes see the `compilation `_ section of the user manual or the `notebook tutorials `_ +For more on pytket passes see the `compilation `_ section of the user manual or the `notebook tutorials `_ .. automodule:: pytket._tket.passes diff --git a/pytket/docs/placement.rst b/pytket/docs/placement.rst index 8038cc293d..36394c0484 100644 --- a/pytket/docs/placement.rst +++ b/pytket/docs/placement.rst @@ -3,7 +3,7 @@ pytket.placement In order for the constraints of a :py:class:`Backend` to be solved we must first assign device qubits to device-independent (or program) qubits. This module contains three placement methods to perform such an assignment. -For more on qubit placement (and routing in general) see the `qubit mapping and routing `_ tutorial and the corresponding entry in the `user manual `_. +For more on qubit placement (and routing in general) see the `qubit mapping and routing `_ tutorial and the corresponding entry in the `user manual `_. .. automodule:: pytket._tket.placement :members: diff --git a/pytket/docs/poetry.lock b/pytket/docs/poetry.lock index 5f561d0a13..ff5df8057a 100644 --- a/pytket/docs/poetry.lock +++ b/pytket/docs/poetry.lock @@ -144,24 +144,6 @@ charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] -[[package]] -name = "bleach" -version = "6.1.0" -description = "An easy safelist-based HTML-sanitizing tool." -optional = false -python-versions = ">=3.8" -files = [ - {file = "bleach-6.1.0-py3-none-any.whl", hash = "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6"}, - {file = "bleach-6.1.0.tar.gz", hash = "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe"}, -] - -[package.dependencies] -six = ">=1.9.0" -webencodings = "*" - -[package.extras] -css = ["tinycss2 (>=1.1.0,<1.3)"] - [[package]] name = "cachecontrol" version = "0.14.0" @@ -357,6 +339,20 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" @@ -445,17 +441,6 @@ files = [ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] -[[package]] -name = "defusedxml" -version = "0.7.1" -description = "XML bomb protection for Python stdlib modules" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, - {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, -] - [[package]] name = "dict2css" version = "0.3.0.post1" @@ -599,20 +584,90 @@ sphinx = ">=6.0,<8.0" sphinx-basic-ng = ">=1.0.0.beta2" [[package]] -name = "graphviz" -version = "0.20.3" -description = "Simple Python interface for Graphviz" +name = "greenlet" +version = "3.1.1" +description = "Lightweight in-process concurrent programming" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "graphviz-0.20.3-py3-none-any.whl", hash = "sha256:81f848f2904515d8cd359cc611faba817598d2feaac4027b266aa3eda7b3dde5"}, - {file = "graphviz-0.20.3.zip", hash = "sha256:09d6bc81e6a9fa392e7ba52135a9d49f1ed62526f96499325930e87ca1b5925d"}, + {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, + {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, + {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, + {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, + {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, + {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, + {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, + {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, + {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, + {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, + {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, + {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, + {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, + {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, + {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, + {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, + {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, ] [package.extras] -dev = ["flake8", "pep8-naming", "tox (>=3)", "twine", "wheel"] -docs = ["sphinx (>=5,<7)", "sphinx-autodoc-typehints", "sphinx-rtd-theme"] -test = ["coverage", "pytest (>=7,<8.1)", "pytest-cov", "pytest-mock (>=3)"] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] [[package]] name = "html5lib" @@ -657,6 +712,29 @@ files = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] +[[package]] +name = "importlib-metadata" +version = "8.5.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + [[package]] name = "ipykernel" version = "6.29.5" @@ -728,27 +806,6 @@ qtconsole = ["qtconsole"] test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] -[[package]] -name = "ipywidgets" -version = "8.1.3" -description = "Jupyter interactive widgets" -optional = false -python-versions = ">=3.7" -files = [ - {file = "ipywidgets-8.1.3-py3-none-any.whl", hash = "sha256:efafd18f7a142248f7cb0ba890a68b96abd4d6e88ddbda483c9130d12667eaf2"}, - {file = "ipywidgets-8.1.3.tar.gz", hash = "sha256:f5f9eeaae082b1823ce9eac2575272952f40d748893972956dc09700a6392d9c"}, -] - -[package.dependencies] -comm = ">=0.1.3" -ipython = ">=6.1.0" -jupyterlab-widgets = ">=3.0.11,<3.1.0" -traitlets = ">=4.3.1" -widgetsnbextension = ">=4.0.11,<4.1.0" - -[package.extras] -test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] - [[package]] name = "jedi" version = "0.19.1" @@ -820,6 +877,33 @@ files = [ [package.dependencies] referencing = ">=0.31.0" +[[package]] +name = "jupyter-cache" +version = "1.0.0" +description = "A defined interface for working with a cache of jupyter notebooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "jupyter_cache-1.0.0-py3-none-any.whl", hash = "sha256:594b1c4e29b488b36547e12477645f489dbdc62cc939b2408df5679f79245078"}, + {file = "jupyter_cache-1.0.0.tar.gz", hash = "sha256:d0fa7d7533cd5798198d8889318269a8c1382ed3b22f622c09a9356521f48687"}, +] + +[package.dependencies] +attrs = "*" +click = "*" +importlib-metadata = "*" +nbclient = ">=0.2" +nbformat = "*" +pyyaml = "*" +sqlalchemy = ">=1.3.12,<3" +tabulate = "*" + +[package.extras] +cli = ["click-log"] +code-style = ["pre-commit (>=2.12)"] +rtd = ["ipykernel", "jupytext", "myst-nb", "nbdime", "sphinx-book-theme", "sphinx-copybutton"] +testing = ["coverage", "ipykernel", "jupytext", "matplotlib", "nbdime", "nbformat (>=5.1)", "numpy", "pandas", "pytest (>=6,<8)", "pytest-cov", "pytest-regressions", "sympy"] + [[package]] name = "jupyter-client" version = "8.6.2" @@ -863,66 +947,28 @@ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphin test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] [[package]] -name = "jupyter-sphinx" -version = "0.5.3" -description = "Jupyter Sphinx Extensions" +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_sphinx-0.5.3-py3-none-any.whl", hash = "sha256:a67b3208d4da5b3508dbb8260d3b359ae476c36c6c642747b78a2520e5be0b05"}, - {file = "jupyter_sphinx-0.5.3.tar.gz", hash = "sha256:2e23699a3a1cf5db31b10981da5aa32606ee730f6b73a844d1e76d800756af56"}, + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] [package.dependencies] -ipykernel = ">=4.5.1" -ipython = "*" -ipywidgets = ">=7.0.0" -nbconvert = ">=5.5" -nbformat = "*" -sphinx = ">=7" +mdurl = ">=0.1,<1.0" [package.extras] -doc = ["matplotlib"] -test = ["bash-kernel", "pytest"] - -[[package]] -name = "jupyterlab-pygments" -version = "0.3.0" -description = "Pygments theme using JupyterLab CSS variables" -optional = false -python-versions = ">=3.8" -files = [ - {file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"}, - {file = "jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d"}, -] - -[[package]] -name = "jupyterlab-widgets" -version = "3.0.11" -description = "Jupyter interactive widgets for JupyterLab" -optional = false -python-versions = ">=3.7" -files = [ - {file = "jupyterlab_widgets-3.0.11-py3-none-any.whl", hash = "sha256:78287fd86d20744ace330a61625024cf5521e1c012a352ddc0a3cdc2348becd0"}, - {file = "jupyterlab_widgets-3.0.11.tar.gz", hash = "sha256:dd5ac679593c969af29c9bed054c24f26842baa51352114736756bc035deee27"}, -] - -[[package]] -name = "lark" -version = "1.1.9" -description = "a modern parsing library" -optional = false -python-versions = ">=3.6" -files = [ - {file = "lark-1.1.9-py3-none-any.whl", hash = "sha256:a0dd3a87289f8ccbb325901e4222e723e7d745dbfc1803eaf5f3d2ace19cf2db"}, - {file = "lark-1.1.9.tar.gz", hash = "sha256:15fa5236490824c2c4aba0e22d2d6d823575dcaf4cdd1848e34b6ad836240fba"}, -] - -[package.extras] -atomic-cache = ["atomicwrites"] -interegular = ["interegular (>=0.3.1,<0.4.0)"] -nearley = ["js2py"] -regex = ["regex"] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" @@ -1008,14 +1054,33 @@ files = [ traitlets = "*" [[package]] -name = "mistune" -version = "3.0.2" -description = "A sane and fast Markdown parser with useful plugins and renderers" +name = "mdit-py-plugins" +version = "0.4.2" +description = "Collection of plugins for markdown-it-py" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, + {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, +] + +[package.dependencies] +markdown-it-py = ">=1.0.0,<4.0.0" + +[package.extras] +code-style = ["pre-commit"] +rtd = ["myst-parser", "sphinx-book-theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" optional = false python-versions = ">=3.7" files = [ - {file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"}, - {file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"}, + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] @@ -1029,23 +1094,6 @@ files = [ {file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"}, ] -[[package]] -name = "mpmath" -version = "1.3.0" -description = "Python library for arbitrary-precision floating-point arithmetic" -optional = false -python-versions = "*" -files = [ - {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, - {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, -] - -[package.extras] -develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] -docs = ["sphinx"] -gmpy = ["gmpy2 (>=2.1.0a4)"] -tests = ["pytest (>=4.6)"] - [[package]] name = "msgpack" version = "1.0.8" @@ -1111,6 +1159,60 @@ files = [ {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] +[[package]] +name = "myst-nb" +version = "1.1.2" +description = "A Jupyter Notebook Sphinx reader built on top of the MyST markdown parser." +optional = false +python-versions = ">=3.9" +files = [ + {file = "myst_nb-1.1.2-py3-none-any.whl", hash = "sha256:9b7034e5d62640cb6daf03f9ca16ef45d0462fced27944c77aa3f98c7cdcd566"}, + {file = "myst_nb-1.1.2.tar.gz", hash = "sha256:961b4005657029ca89892a4c75edbf0856c54ceaf6172368b46bf7676c1f7700"}, +] + +[package.dependencies] +importlib_metadata = "*" +ipykernel = "*" +ipython = "*" +jupyter-cache = ">=0.5" +myst-parser = ">=1.0.0" +nbclient = "*" +nbformat = ">=5.0" +pyyaml = "*" +sphinx = ">=5" +typing-extensions = "*" + +[package.extras] +code-style = ["pre-commit"] +rtd = ["alabaster", "altair", "bokeh", "coconut (>=1.4.3)", "ipykernel (>=5.5)", "ipywidgets", "jupytext (>=1.11.2)", "matplotlib", "numpy", "pandas", "plotly", "sphinx-book-theme (>=0.3)", "sphinx-copybutton", "sphinx-design", "sphinxcontrib-bibtex", "sympy"] +testing = ["beautifulsoup4", "coverage (>=6.4)", "ipykernel (>=5.5)", "ipython (!=8.1.0)", "ipywidgets (>=8)", "jupytext (>=1.11.2)", "matplotlib (==3.7.*)", "nbdime", "numpy", "pandas", "pyarrow", "pytest", "pytest-cov (>=3)", "pytest-param-files", "pytest-regressions", "sympy (>=1.10.1)"] + +[[package]] +name = "myst-parser" +version = "4.0.0" +description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," +optional = false +python-versions = ">=3.10" +files = [ + {file = "myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d"}, + {file = "myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531"}, +] + +[package.dependencies] +docutils = ">=0.19,<0.22" +jinja2 = "*" +markdown-it-py = ">=3.0,<4.0" +mdit-py-plugins = ">=0.4.1,<1.0" +pyyaml = "*" +sphinx = ">=7,<9" + +[package.extras] +code-style = ["pre-commit (>=3.0,<4.0)"] +linkify = ["linkify-it-py (>=2.0,<3.0)"] +rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] +testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] +testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] + [[package]] name = "natsort" version = "8.4.0" @@ -1148,43 +1250,6 @@ dev = ["pre-commit"] docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling"] test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0,<8)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] -[[package]] -name = "nbconvert" -version = "7.16.4" -description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." -optional = false -python-versions = ">=3.8" -files = [ - {file = "nbconvert-7.16.4-py3-none-any.whl", hash = "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3"}, - {file = "nbconvert-7.16.4.tar.gz", hash = "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4"}, -] - -[package.dependencies] -beautifulsoup4 = "*" -bleach = "!=5.0.0" -defusedxml = "*" -jinja2 = ">=3.0" -jupyter-core = ">=4.7" -jupyterlab-pygments = "*" -markupsafe = ">=2.0" -mistune = ">=2.0.3,<4" -nbclient = ">=0.5.0" -nbformat = ">=5.7" -packaging = "*" -pandocfilters = ">=1.4.1" -pygments = ">=2.4.1" -tinycss2 = "*" -traitlets = ">=5.1" - -[package.extras] -all = ["flaky", "ipykernel", "ipython", "ipywidgets (>=7.5)", "myst-parser", "nbsphinx (>=0.2.12)", "playwright", "pydata-sphinx-theme", "pyqtwebengine (>=5.15)", "pytest (>=7)", "sphinx (==5.0.2)", "sphinxcontrib-spelling", "tornado (>=6.1)"] -docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sphinx-theme", "sphinx (==5.0.2)", "sphinxcontrib-spelling"] -qtpdf = ["pyqtwebengine (>=5.15)"] -qtpng = ["pyqtwebengine (>=5.15)"] -serve = ["tornado (>=6.1)"] -test = ["flaky", "ipykernel", "ipywidgets (>=7.5)", "pytest (>=7)"] -webpdf = ["playwright"] - [[package]] name = "nbformat" version = "5.10.4" @@ -1217,78 +1282,6 @@ files = [ {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, ] -[[package]] -name = "networkx" -version = "3.3" -description = "Python package for creating and manipulating graphs and networks" -optional = false -python-versions = ">=3.10" -files = [ - {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"}, - {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}, -] - -[package.extras] -default = ["matplotlib (>=3.6)", "numpy (>=1.23)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] -developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["myst-nb (>=1.0)", "numpydoc (>=1.7)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=2.0)", "pygraphviz (>=1.12)", "sympy (>=1.10)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] - -[[package]] -name = "numpy" -version = "2.0.1" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "numpy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fbb536eac80e27a2793ffd787895242b7f18ef792563d742c2d673bfcb75134"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69ff563d43c69b1baba77af455dd0a839df8d25e8590e79c90fcbe1499ebde42"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:1b902ce0e0a5bb7704556a217c4f63a7974f8f43e090aff03fcf262e0b135e02"}, - {file = "numpy-2.0.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:f1659887361a7151f89e79b276ed8dff3d75877df906328f14d8bb40bb4f5101"}, - {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4658c398d65d1b25e1760de3157011a80375da861709abd7cef3bad65d6543f9"}, - {file = "numpy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4127d4303b9ac9f94ca0441138acead39928938660ca58329fe156f84b9f3015"}, - {file = "numpy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e5eeca8067ad04bc8a2a8731183d51d7cbaac66d86085d5f4766ee6bf19c7f87"}, - {file = "numpy-2.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9adbd9bb520c866e1bfd7e10e1880a1f7749f1f6e5017686a5fbb9b72cf69f82"}, - {file = "numpy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:7b9853803278db3bdcc6cd5beca37815b133e9e77ff3d4733c247414e78eb8d1"}, - {file = "numpy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81b0893a39bc5b865b8bf89e9ad7807e16717f19868e9d234bdaf9b1f1393868"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75b4e316c5902d8163ef9d423b1c3f2f6252226d1aa5cd8a0a03a7d01ffc6268"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e4eeb6eb2fced786e32e6d8df9e755ce5be920d17f7ce00bc38fcde8ccdbf9e"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1e01dcaab205fbece13c1410253a9eea1b1c9b61d237b6fa59bcc46e8e89343"}, - {file = "numpy-2.0.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8fc2de81ad835d999113ddf87d1ea2b0f4704cbd947c948d2f5513deafe5a7b"}, - {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a3d94942c331dd4e0e1147f7a8699a4aa47dffc11bf8a1523c12af8b2e91bbe"}, - {file = "numpy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15eb4eca47d36ec3f78cde0a3a2ee24cf05ca7396ef808dda2c0ddad7c2bde67"}, - {file = "numpy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b83e16a5511d1b1f8a88cbabb1a6f6a499f82c062a4251892d9ad5d609863fb7"}, - {file = "numpy-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f87fec1f9bc1efd23f4227becff04bd0e979e23ca50cc92ec88b38489db3b55"}, - {file = "numpy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:36d3a9405fd7c511804dc56fc32974fa5533bdeb3cd1604d6b8ff1d292b819c4"}, - {file = "numpy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:08458fbf403bff5e2b45f08eda195d4b0c9b35682311da5a5a0a0925b11b9bd8"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bf4e6f4a2a2e26655717a1983ef6324f2664d7011f6ef7482e8c0b3d51e82ac"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6fddc5fe258d3328cd8e3d7d3e02234c5d70e01ebe377a6ab92adb14039cb4"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5daab361be6ddeb299a918a7c0864fa8618af66019138263247af405018b04e1"}, - {file = "numpy-2.0.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:ea2326a4dca88e4a274ba3a4405eb6c6467d3ffbd8c7d38632502eaae3820587"}, - {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529af13c5f4b7a932fb0e1911d3a75da204eff023ee5e0e79c1751564221a5c8"}, - {file = "numpy-2.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6790654cb13eab303d8402354fabd47472b24635700f631f041bd0b65e37298a"}, - {file = "numpy-2.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cbab9fc9c391700e3e1287666dfd82d8666d10e69a6c4a09ab97574c0b7ee0a7"}, - {file = "numpy-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99d0d92a5e3613c33a5f01db206a33f8fdf3d71f2912b0de1739894668b7a93b"}, - {file = "numpy-2.0.1-cp312-cp312-win32.whl", hash = "sha256:173a00b9995f73b79eb0191129f2455f1e34c203f559dd118636858cc452a1bf"}, - {file = "numpy-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bb2124fdc6e62baae159ebcfa368708867eb56806804d005860b6007388df171"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc085b28d62ff4009364e7ca34b80a9a080cbd97c2c0630bb5f7f770dae9414"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fae4ebbf95a179c1156fab0b142b74e4ba4204c87bde8d3d8b6f9c34c5825ef"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:72dc22e9ec8f6eaa206deb1b1355eb2e253899d7347f5e2fae5f0af613741d06"}, - {file = "numpy-2.0.1-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:ec87f5f8aca726117a1c9b7083e7656a9d0d606eec7299cc067bb83d26f16e0c"}, - {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f682ea61a88479d9498bf2091fdcd722b090724b08b31d63e022adc063bad59"}, - {file = "numpy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8efc84f01c1cd7e34b3fb310183e72fcdf55293ee736d679b6d35b35d80bba26"}, - {file = "numpy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3fdabe3e2a52bc4eff8dc7a5044342f8bd9f11ef0934fcd3289a788c0eb10018"}, - {file = "numpy-2.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:24a0e1befbfa14615b49ba9659d3d8818a0f4d8a1c5822af8696706fbda7310c"}, - {file = "numpy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:f9cf5ea551aec449206954b075db819f52adc1638d46a6738253a712d553c7b4"}, - {file = "numpy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e9e81fa9017eaa416c056e5d9e71be93d05e2c3c2ab308d23307a8bc4443c368"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61728fba1e464f789b11deb78a57805c70b2ed02343560456190d0501ba37b0f"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:12f5d865d60fb9734e60a60f1d5afa6d962d8d4467c120a1c0cda6eb2964437d"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eacf3291e263d5a67d8c1a581a8ebbcfd6447204ef58828caf69a5e3e8c75990"}, - {file = "numpy-2.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2c3a346ae20cfd80b6cfd3e60dc179963ef2ea58da5ec074fd3d9e7a1e7ba97f"}, - {file = "numpy-2.0.1.tar.gz", hash = "sha256:485b87235796410c3519a699cfe1faab097e509e90ebb05dcd098db2ae87e7b3"}, -] - [[package]] name = "packaging" version = "24.1" @@ -1300,17 +1293,6 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] -[[package]] -name = "pandocfilters" -version = "1.5.1" -description = "Utilities for writing pandoc filters in python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"}, - {file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e"}, -] - [[package]] name = "parso" version = "0.8.4" @@ -1463,44 +1445,6 @@ files = [ [package.dependencies] six = ">=1.5" -[[package]] -name = "pytket" -version = "1.31.0" -description = "Quantum computing toolkit and interface to the TKET compiler" -optional = false -python-versions = ">=3.10" -files = [ - {file = "pytket-1.31.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6a1981f2e8fcfa4bfa2bf7c2732f7062646415d2846a717d9542f44fb2c506a7"}, - {file = "pytket-1.31.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:625fa01d930cafb99538fe9d6134039d25feb27bba528f32a78e1679d4103d0f"}, - {file = "pytket-1.31.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33762b67a39fe0052afa39a21e6b0f9c045d7c5639a7571ece4cf1bcb0b68e3f"}, - {file = "pytket-1.31.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9878468c7fda0eb8ca3f0b9c349f811a1e7a7c896463892bfea9b66f88c99447"}, - {file = "pytket-1.31.0-cp310-cp310-win_amd64.whl", hash = "sha256:8cc42819d6ca0e4c2587235fc78ec6ae2b75610190f0a0ab369911043e6a7dc0"}, - {file = "pytket-1.31.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:be5fab98fcc14860ee4effe0a7657b39c2487a29c048e0ab228b7be3ec80e049"}, - {file = "pytket-1.31.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:258ee145704e040d352c164e11d794bf612d26cc37c1f585a5453397a86f7113"}, - {file = "pytket-1.31.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a2f7ff90a2d73447d1e45c2f56ce08116634a4c2986312fc88a7903a328b4b5"}, - {file = "pytket-1.31.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5069fd53997ae93826df24ff7e2b5309d4d0ae645b6c10d3ac62725688b2556f"}, - {file = "pytket-1.31.0-cp311-cp311-win_amd64.whl", hash = "sha256:e4e361269f611869495ac34aaa68a07994f76bd35d21962629d2aa54a62d5726"}, - {file = "pytket-1.31.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:848350011e8f04f59ec43deb79b1fd1ff00382f71fd208adfa972c0b74efa09d"}, - {file = "pytket-1.31.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:88957b28fec827a55eceac7d4c70e29ddf89531cb62c325585cab7600a4c55b9"}, - {file = "pytket-1.31.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f674664465c04336f7d85b6f0aafdd28f5437acd647c9083df1433a90d10a98d"}, - {file = "pytket-1.31.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fa61c7a629402fb260a71c6425466ad205ef148463959f1b29717fd66a79681"}, - {file = "pytket-1.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:c414598ea4a66a8075943cdee067911af597ca9dc0a22e9ffa4596c8e995abe2"}, -] - -[package.dependencies] -graphviz = ">=0.20.3" -jinja2 = ">=3.1.4" -lark = ">=1.1.9" -networkx = ">=2.8.8" -numpy = ">=1.26.4" -qwasm = ">=1.0.1" -scipy = ">=1.13.1" -sympy = ">=1.12.1" -typing-extensions = ">=4.12.2" - -[package.extras] -zx = ["autoray (>=0.6.12)", "numba (>=0.60.0)", "quimb (>=1.8.2)"] - [[package]] name = "pywin32" version = "306" @@ -1524,6 +1468,68 @@ files = [ {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, ] +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + [[package]] name = "pyzmq" version = "26.0.3" @@ -1624,20 +1630,6 @@ files = [ [package.dependencies] cffi = {version = "*", markers = "implementation_name == \"pypy\""} -[[package]] -name = "qwasm" -version = "1.0.1" -description = "WebAssembly decoder & disassembler" -optional = false -python-versions = "*" -files = [ - {file = "qwasm-1.0.1-py3-none-any.whl", hash = "sha256:c4c82a3f962d29314634868e06375f0cb4676c3d5266fbe137f6cd67321b0ef1"}, - {file = "qwasm-1.0.1.tar.gz", hash = "sha256:01f5dfe27159b7fdd9d02cd299833225d528fa383d1278268e5e1526357950fb"}, -] - -[package.dependencies] -setuptools = "*" - [[package]] name = "referencing" version = "0.35.1" @@ -1863,64 +1855,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, ] -[[package]] -name = "scipy" -version = "1.14.0" -description = "Fundamental algorithms for scientific computing in Python" -optional = false -python-versions = ">=3.10" -files = [ - {file = "scipy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7e911933d54ead4d557c02402710c2396529540b81dd554fc1ba270eb7308484"}, - {file = "scipy-1.14.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:687af0a35462402dd851726295c1a5ae5f987bd6e9026f52e9505994e2f84ef6"}, - {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:07e179dc0205a50721022344fb85074f772eadbda1e1b3eecdc483f8033709b7"}, - {file = "scipy-1.14.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a9c9a9b226d9a21e0a208bdb024c3982932e43811b62d202aaf1bb59af264b1"}, - {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:076c27284c768b84a45dcf2e914d4000aac537da74236a0d45d82c6fa4b7b3c0"}, - {file = "scipy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42470ea0195336df319741e230626b6225a740fd9dce9642ca13e98f667047c0"}, - {file = "scipy-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:176c6f0d0470a32f1b2efaf40c3d37a24876cebf447498a4cefb947a79c21e9d"}, - {file = "scipy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ad36af9626d27a4326c8e884917b7ec321d8a1841cd6dacc67d2a9e90c2f0359"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d056a8709ccda6cf36cdd2eac597d13bc03dba38360f418560a93050c76a16e"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f0a50da861a7ec4573b7c716b2ebdcdf142b66b756a0d392c236ae568b3a93fb"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:94c164a9e2498e68308e6e148646e486d979f7fcdb8b4cf34b5441894bdb9caf"}, - {file = "scipy-1.14.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a7d46c3e0aea5c064e734c3eac5cf9eb1f8c4ceee756262f2c7327c4c2691c86"}, - {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eee2989868e274aae26125345584254d97c56194c072ed96cb433f32f692ed8"}, - {file = "scipy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3154691b9f7ed73778d746da2df67a19d046a6c8087c8b385bc4cdb2cfca74"}, - {file = "scipy-1.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c40003d880f39c11c1edbae8144e3813904b10514cd3d3d00c277ae996488cdb"}, - {file = "scipy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:5b083c8940028bb7e0b4172acafda6df762da1927b9091f9611b0bcd8676f2bc"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bff2438ea1330e06e53c424893ec0072640dac00f29c6a43a575cbae4c99b2b9"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bbc0471b5f22c11c389075d091d3885693fd3f5e9a54ce051b46308bc787e5d4"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:64b2ff514a98cf2bb734a9f90d32dc89dc6ad4a4a36a312cd0d6327170339eb0"}, - {file = "scipy-1.14.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:7d3da42fbbbb860211a811782504f38ae7aaec9de8764a9bef6b262de7a2b50f"}, - {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d91db2c41dd6c20646af280355d41dfa1ec7eead235642178bd57635a3f82209"}, - {file = "scipy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a01cc03bcdc777c9da3cfdcc74b5a75caffb48a6c39c8450a9a05f82c4250a14"}, - {file = "scipy-1.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65df4da3c12a2bb9ad52b86b4dcf46813e869afb006e58be0f516bc370165159"}, - {file = "scipy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:4c4161597c75043f7154238ef419c29a64ac4a7c889d588ea77690ac4d0d9b20"}, - {file = "scipy-1.14.0.tar.gz", hash = "sha256:b5923f48cb840380f9854339176ef21763118a7300a88203ccd0bdd26e58527b"}, -] - -[package.dependencies] -numpy = ">=1.23.5,<2.3" - -[package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - -[[package]] -name = "setuptools" -version = "72.1.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, - {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, -] - -[package.extras] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "six" version = "1.16.0" @@ -1989,19 +1923,6 @@ docs = ["sphinxcontrib-websupport"] lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] -[[package]] -name = "sphinx-autodoc-annotation" -version = "1.0-1" -description = "Use Python 3 annotations in sphinx-enabled docstrings" -optional = false -python-versions = "*" -files = [ - {file = "sphinx-autodoc-annotation-1.0-1.tar.gz", hash = "sha256:4a3d03081efe1e5f2bc9b9d00746550f45b9f543b0c79519c523168ca7f7d89a"}, -] - -[package.dependencies] -sphinx = ">=1.1" - [[package]] name = "sphinx-autodoc-typehints" version = "2.2.3" @@ -2236,6 +2157,93 @@ lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] +[[package]] +name = "sqlalchemy" +version = "2.0.35" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.35-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67219632be22f14750f0d1c70e62f204ba69d28f62fd6432ba05ab295853de9b"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4668bd8faf7e5b71c0319407b608f278f279668f358857dbfd10ef1954ac9f90"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8bea573863762bbf45d1e13f87c2d2fd32cee2dbd50d050f83f87429c9e1ea"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f552023710d4b93d8fb29a91fadf97de89c5926c6bd758897875435f2a939f33"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:016b2e665f778f13d3c438651dd4de244214b527a275e0acf1d44c05bc6026a9"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7befc148de64b6060937231cbff8d01ccf0bfd75aa26383ffdf8d82b12ec04ff"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-win32.whl", hash = "sha256:22b83aed390e3099584b839b93f80a0f4a95ee7f48270c97c90acd40ee646f0b"}, + {file = "SQLAlchemy-2.0.35-cp310-cp310-win_amd64.whl", hash = "sha256:a29762cd3d116585278ffb2e5b8cc311fb095ea278b96feef28d0b423154858e"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e21f66748ab725ade40fa7af8ec8b5019c68ab00b929f6643e1b1af461eddb60"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a6219108a15fc6d24de499d0d515c7235c617b2540d97116b663dade1a54d62"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042622a5306c23b972192283f4e22372da3b8ddf5f7aac1cc5d9c9b222ab3ff6"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:627dee0c280eea91aed87b20a1f849e9ae2fe719d52cbf847c0e0ea34464b3f7"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4fdcd72a789c1c31ed242fd8c1bcd9ea186a98ee8e5408a50e610edfef980d71"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89b64cd8898a3a6f642db4eb7b26d1b28a497d4022eccd7717ca066823e9fb01"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-win32.whl", hash = "sha256:6a93c5a0dfe8d34951e8a6f499a9479ffb9258123551fa007fc708ae2ac2bc5e"}, + {file = "SQLAlchemy-2.0.35-cp311-cp311-win_amd64.whl", hash = "sha256:c68fe3fcde03920c46697585620135b4ecfdfc1ed23e75cc2c2ae9f8502c10b8"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:eb60b026d8ad0c97917cb81d3662d0b39b8ff1335e3fabb24984c6acd0c900a2"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6921ee01caf375363be5e9ae70d08ce7ca9d7e0e8983183080211a062d299468"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cdf1a0dbe5ced887a9b127da4ffd7354e9c1a3b9bb330dce84df6b70ccb3a8d"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93a71c8601e823236ac0e5d087e4f397874a421017b3318fd92c0b14acf2b6db"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e04b622bb8a88f10e439084486f2f6349bf4d50605ac3e445869c7ea5cf0fa8c"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1b56961e2d31389aaadf4906d453859f35302b4eb818d34a26fab72596076bb8"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-win32.whl", hash = "sha256:0f9f3f9a3763b9c4deb8c5d09c4cc52ffe49f9876af41cc1b2ad0138878453cf"}, + {file = "SQLAlchemy-2.0.35-cp312-cp312-win_amd64.whl", hash = "sha256:25b0f63e7fcc2a6290cb5f7f5b4fc4047843504983a28856ce9b35d8f7de03cc"}, + {file = "SQLAlchemy-2.0.35-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f021d334f2ca692523aaf7bbf7592ceff70c8594fad853416a81d66b35e3abf9"}, + {file = "SQLAlchemy-2.0.35-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05c3f58cf91683102f2f0265c0db3bd3892e9eedabe059720492dbaa4f922da1"}, + {file = "SQLAlchemy-2.0.35-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:032d979ce77a6c2432653322ba4cbeabf5a6837f704d16fa38b5a05d8e21fa00"}, + {file = "SQLAlchemy-2.0.35-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:2e795c2f7d7249b75bb5f479b432a51b59041580d20599d4e112b5f2046437a3"}, + {file = "SQLAlchemy-2.0.35-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:cc32b2990fc34380ec2f6195f33a76b6cdaa9eecf09f0c9404b74fc120aef36f"}, + {file = "SQLAlchemy-2.0.35-cp37-cp37m-win32.whl", hash = "sha256:9509c4123491d0e63fb5e16199e09f8e262066e58903e84615c301dde8fa2e87"}, + {file = "SQLAlchemy-2.0.35-cp37-cp37m-win_amd64.whl", hash = "sha256:3655af10ebcc0f1e4e06c5900bb33e080d6a1fa4228f502121f28a3b1753cde5"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4c31943b61ed8fdd63dfd12ccc919f2bf95eefca133767db6fbbd15da62078ec"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a62dd5d7cc8626a3634208df458c5fe4f21200d96a74d122c83bc2015b333bc1"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0630774b0977804fba4b6bbea6852ab56c14965a2b0c7fc7282c5f7d90a1ae72"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d625eddf7efeba2abfd9c014a22c0f6b3796e0ffb48f5d5ab106568ef01ff5a"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ada603db10bb865bbe591939de854faf2c60f43c9b763e90f653224138f910d9"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c41411e192f8d3ea39ea70e0fae48762cd11a2244e03751a98bd3c0ca9a4e936"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-win32.whl", hash = "sha256:d299797d75cd747e7797b1b41817111406b8b10a4f88b6e8fe5b5e59598b43b0"}, + {file = "SQLAlchemy-2.0.35-cp38-cp38-win_amd64.whl", hash = "sha256:0375a141e1c0878103eb3d719eb6d5aa444b490c96f3fedab8471c7f6ffe70ee"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ccae5de2a0140d8be6838c331604f91d6fafd0735dbdcee1ac78fc8fbaba76b4"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2a275a806f73e849e1c309ac11108ea1a14cd7058577aba962cd7190e27c9e3c"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:732e026240cdd1c1b2e3ac515c7a23820430ed94292ce33806a95869c46bd139"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:890da8cd1941fa3dab28c5bac3b9da8502e7e366f895b3b8e500896f12f94d11"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0d8326269dbf944b9201911b0d9f3dc524d64779a07518199a58384c3d37a44"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b76d63495b0508ab9fc23f8152bac63205d2a704cd009a2b0722f4c8e0cba8e0"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-win32.whl", hash = "sha256:69683e02e8a9de37f17985905a5eca18ad651bf592314b4d3d799029797d0eb3"}, + {file = "SQLAlchemy-2.0.35-cp39-cp39-win_amd64.whl", hash = "sha256:aee110e4ef3c528f3abbc3c2018c121e708938adeeff9006428dd7c8555e9b3f"}, + {file = "SQLAlchemy-2.0.35-py3-none-any.whl", hash = "sha256:2ab3f0336c0387662ce6221ad30ab3a5e6499aab01b9790879b6578fd9b8faa1"}, + {file = "sqlalchemy-2.0.35.tar.gz", hash = "sha256:e11d7ea4d24f0a262bccf9a7cd6284c976c5369dac21db237cff59586045ab9f"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + [[package]] name = "stack-data" version = "0.6.3" @@ -2266,23 +2274,6 @@ files = [ {file = "standard_imghdr-3.10.14.tar.gz", hash = "sha256:2598fe2e7c540dbda34b233295e10957ab8dc8ac6f3bd9eaa8d38be167232e52"}, ] -[[package]] -name = "sympy" -version = "1.13.1" -description = "Computer algebra system (CAS) in Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8"}, - {file = "sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f"}, -] - -[package.dependencies] -mpmath = ">=1.1.0,<1.4" - -[package.extras] -dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] - [[package]] name = "tabulate" version = "0.9.0" @@ -2297,24 +2288,6 @@ files = [ [package.extras] widechars = ["wcwidth"] -[[package]] -name = "tinycss2" -version = "1.3.0" -description = "A tiny CSS parser" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7"}, - {file = "tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d"}, -] - -[package.dependencies] -webencodings = ">=0.4" - -[package.extras] -doc = ["sphinx", "sphinx_rtd_theme"] -test = ["pytest", "ruff"] - [[package]] name = "tomli" version = "2.0.1" @@ -2412,17 +2385,25 @@ files = [ ] [[package]] -name = "widgetsnbextension" -version = "4.0.11" -description = "Jupyter interactive widgets for Jupyter Notebook" +name = "zipp" +version = "3.20.2" +description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "widgetsnbextension-4.0.11-py3-none-any.whl", hash = "sha256:55d4d6949d100e0d08b94948a42efc3ed6dfdc0e9468b2c4b128c9a2ce3a7a36"}, - {file = "widgetsnbextension-4.0.11.tar.gz", hash = "sha256:8b22a8f1910bfd188e596fe7fc05dcbd87e810c8a4ba010bdb3da86637398474"}, + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, ] +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "9b837279be04212032b0d6f546e8a88348c3ad96b67caa86d6c3ed8fddd9afdb" +content-hash = "27394b648cb72d80fa8c56782b90331a697d118bffba4c1ee1859b904de6384c" diff --git a/pytket/docs/predicates.rst b/pytket/docs/predicates.rst index 7402373c59..e0fa3066ec 100644 --- a/pytket/docs/predicates.rst +++ b/pytket/docs/predicates.rst @@ -3,7 +3,7 @@ pytket.predicates In pytket, predicates enforce properties of circuits. Each pytket :py:class:`Backend` has its own set of predicates which must be satisfied before a quantum circuit can be executed. There are predicates that enforce restrictions including gateset, number of qubits and classical control. -For more on predicates read the corresponding section of the `user manual `_. See also the `Compilation example `_ notebook. +For more on predicates read the corresponding section of the `user manual `_. See also the `Compilation example `_ notebook. .. automodule:: pytket._tket.predicates :members: diff --git a/pytket/docs/pyproject.toml b/pytket/docs/pyproject.toml index 6fe1081695..0b360a6cc4 100644 --- a/pytket/docs/pyproject.toml +++ b/pytket/docs/pyproject.toml @@ -1,4 +1,5 @@ [tool.poetry] +package-mode = false name = "docs" version = "0.1.0" description = "" @@ -8,13 +9,11 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.10" sphinx = "^7.3.7" -sphinx-autodoc-annotation = "^1.0.post1" sphinx-copybutton = "^0.5.2" -jupyter-sphinx = "^0.5.3" ipykernel = "^6.29.4" -enum-tools = {extras = ["sphinx"], version = "^0.12.0"} +enum-tools = { extras = ["sphinx"], version = "^0.12.0" } furo = "^2024.5.6" -pytket = "^1.31.0" +myst-nb = "^1.1.2" [build-system] requires = ["poetry-core"] diff --git a/pytket/docs/pytket-docs-theming b/pytket/docs/pytket-docs-theming new file mode 160000 index 0000000000..437d49873e --- /dev/null +++ b/pytket/docs/pytket-docs-theming @@ -0,0 +1 @@ +Subproject commit 437d49873ee67c60e8c1e062bdfd6e58664ffc28 diff --git a/pytket/docs/qasm.rst b/pytket/docs/qasm.rst index 80d3f76db9..12a9594447 100644 --- a/pytket/docs/qasm.rst +++ b/pytket/docs/qasm.rst @@ -16,7 +16,7 @@ We can set the ``header`` argument in the qasm conversion functions as follows. qasm_str = circuit_to_qasm_str(circ, header="hqslib1") -.. note:: Unlike pytket backends, the qasm converters do not handle `implicit qubit permutations `_. In other words if a circuit containing an implicit qubit permutation is converted to a qasm file the implicit permutation will not be accounted for and the circuit will be missing this permutation when reimported. +.. note:: Unlike pytket backends, the qasm converters do not handle `implicit qubit permutations `_. In other words if a circuit containing an implicit qubit permutation is converted to a qasm file the implicit permutation will not be accounted for and the circuit will be missing this permutation when reimported. .. automodule:: pytket.qasm :members: circuit_from_qasm, circuit_from_qasm_wasm, circuit_to_qasm, circuit_from_qasm_str, circuit_to_qasm_str, circuit_from_qasm_io, circuit_to_qasm_io diff --git a/pytket/docs/quantinuum-sphinx b/pytket/docs/quantinuum-sphinx deleted file mode 160000 index a8ac27e089..0000000000 --- a/pytket/docs/quantinuum-sphinx +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a8ac27e089824099708da2cea9cef262e4df3ad2 diff --git a/pytket/package.md b/pytket/package.md index 8a01c74bc6..3c01575718 100644 --- a/pytket/package.md +++ b/pytket/package.md @@ -10,13 +10,13 @@ To install run the pip command: `` pip install pytket`` -See [Installation troubleshooting](https://tket.quantinuum.com/api-docs/install.html) for help with installation. +See [Installation troubleshooting](https://docs.quantinuum.com/tket/api-docs/install.html) for help with installation. To install the pytket extension modules add a hyphen and the extension name to the command: `` pip install pytket-quantinuum `` -For a list of pytket extensions see this page: https://tket.quantinuum.com/api-docs/extensions. +For a list of pytket extensions see this page: https://docs.quantinuum.com/tket/api-docs/extensions. _Warning._ There is a [known issue](https://github.com/CQCL/tket/issues/926) with installing pytket in a conda environment on MacOS: you may not be able to @@ -25,9 +25,9 @@ official Python distribution instead. ## Documentation and Examples -API reference: https://tket.quantinuum.com/api-docs/ +API reference: https://docs.quantinuum.com/tket/api-docs/ -To get started using pytket see the [user guide](https://tket.quantinuum.com/user-guide/). +To get started using pytket see the [user guide](https://docs.quantinuum.com/tket/user-guide/). ## Support and Discussion From cf38f315a0ccdbdc41ef5aaa45e2b744047581b2 Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Thu, 24 Oct 2024 16:47:28 +0100 Subject: [PATCH 12/15] Update/docmain1.34 (#1638) * Bump cachix/install-nix-action from 29 to 30 (#1606) Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 29 to 30. - [Release notes](https://github.com/cachix/install-nix-action/releases) - [Commits](https://github.com/cachix/install-nix-action/compare/v29...v30) --- updated-dependencies: - dependency-name: cachix/install-nix-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix initialization of subcircuit in `GuidedPauliSimp` implementation (#1607) * Update docs and changelog for 1.33.1 release. (#1612) * fix: update wasm functions to accept `WasmModuleHandler` (#1613) * Update macos CI runners (#1616) * Fix default value of wire_comments. (#1618) * docs: use theming submodule, update docs build and linking (#1609) * remove quantinuum-sphinx * add pytket-docs-theming submodule * delete _static and conf.py * ignore jupyter cache * use myst-nb for code cells * use latest theming commit * ignore any generated .ipynb files * add myst_nb dependency * remove jupyter sphinx and autodoc annotations dependencies * add build docs script * fix formatting of display.md * update docs build in build and test workflow * remove pypi pytket installation * Hardcode pytket version in flake.nix, as there is no longer a place in the source code that it's written down. * ignore jupyter execute * use latest pytket-docs-theming submodule * update extensions links * update sidebar heading for extensions * update pypi page links * update README links * replace remaining docs links * use latest theming commit * use package-mode=false for poetry * install a develop wheel of pytket * try to fix poetry issue * use poetry without .venv in C.I. * run docs build from correct directory * use latest docs theming update * fix up some extensions links * fix some broken links found by checker * link to H-Series docs * fix two more links * fix typo * latest theming (again) * fix artifact path * use extensions.html * use original README link * remove backends sentance * use >= for poetry --------- Co-authored-by: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> * Support Python 3.13 (#1620) * Bugfix/circuit renderer (#1614) * add default min width/height to circuit renderer config * update docs * changelog * Don't try to install pytket[ZX] with Python 3.13. (#1627) * `ClExprOp` and friends (#1628) * Set MacOS deployment target to 12 for x86. (#1633) * Drop support for MacOS 12 (#1634) * Updated nixpkgs. Added custom mypy build. (#1636) * Updated nixpkgs. Added custom mypy build (required version isn't in nixpkgs) * Used overrideAttrs on nixpkgs' mypy rather than a new derivation as per @johnchildren's suggestion * Refactor greedy pauli simp (#1611) * Initial refactor * Refactor node types * Move nodes definition into a new file * Refactor synthesis * Cleanup * More refactoring * Refactor pauli graph converters * Initial implementation of GPGraph * Migrate to GPGraph * Ignore global phase * Add supports for conditional gates * Add support for classical ops * flatten_registers should only rename qubits and bits * Replace unsigned with Bit * Revert "Replace unsigned with Bit" This reverts commit 9e9a2fd556dfdb47873be8dae01bd04bda1d772e. * Manually check if qubits and bits can be flattened since ``is_simple()`` doesn't work with wasm * Consistent enum names * Add support for mid-circuit measurement * Rename variables * Add support for resets * Update docstrings * Update pass predicate * Add more tests * bump tket version * Add changelog entry * Fix docs errors * bump tket version * Remove unused headers * bump tket version in pytket * uncomment lines * remove consts in SQ_CLIFF_DAGGER * Merging conditionals * Revert "Merging conditionals" This reverts commit f5138f1357edeabd4cee884ce3512535901a447e. * Optimise conditional handling * remove ConditionalPauliRotation * Remove clifford reduction and pass parameters when synthesis conditionals * Seeded tie breaking * Add limits to the search space * Allowing ZZPhase gates * update binder and serialisation * Add test for ops handling in python * add note for AC node cost * bump tket version * update changelog * fix changelog format * add missing prams in docstrings * cast size_t to unsigned * remove nondeterminism from test * Remove more nondeterminism * regen stubs * Add debug info * Revert "Add debug info" This reverts commit e5030e28278cc494d055fadc136fe9327668bb54. * Replace implicit wire swaps in optimised conditional circuits * re-organise changelog * Bump tket version * fix bug in sign correction * Add more tests * Bump version and update changelog for 1.34.0 release (#1637) --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Co-authored-by: Kartik Singhal Co-authored-by: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Co-authored-by: Tiffany Duneau <37022011+DNA386@users.noreply.github.com> Co-authored-by: yao-cqc <75305462+yao-cqc@users.noreply.github.com> --- .github/workflows/build-with-nix.yml | 4 +- .github/workflows/build_and_test.yml | 35 +- .github/workflows/build_libs.yml | 2 +- .github/workflows/packages.yml | 2 +- .github/workflows/pytket_benchmarking.yml | 4 +- .github/workflows/release.yml | 49 +- .github/workflows/test_libs.yml | 2 +- .github/workflows/test_libs_all.yml | 2 +- README.md | 6 +- conan-profiles/macos-13 | 8 + conan-profiles/macos-15 | 8 + flake.lock | 12 +- flake.nix | 2 +- nix-support/pytket.nix | 2 +- nix-support/third-party-python-packages.nix | 10 + pytket/CMakeLists.txt | 1 + pytket/binders/circuit/Circuit/add_op.cpp | 16 + pytket/binders/circuit/clexpr.cpp | 554 +++++ pytket/binders/circuit/main.cpp | 4 + pytket/binders/passes.cpp | 13 +- pytket/binders/transform.cpp | 13 +- pytket/conanfile.py | 2 +- pytket/docs/changelog.rst | 32 + pytket/docs/display.md | 5 +- pytket/docs/getting_started.rst | 4 +- pytket/docs/index.rst | 4 +- pytket/docs/install.rst | 6 +- pytket/package.md | 2 +- pytket/pytket/_tket/circuit.pyi | 304 ++- pytket/pytket/_tket/passes.pyi | 6 +- pytket/pytket/_tket/transform.pyi | 6 +- pytket/pytket/circuit/__init__.py | 8 +- pytket/pytket/circuit/clexpr.py | 163 ++ pytket/pytket/circuit/display/__init__.py | 6 +- pytket/pytket/circuit/logic_exp.py | 4 +- pytket/pytket/qasm/qasm.py | 129 +- pytket/pytket/quipper/quipper.py | 2 +- pytket/pytket/utils/distribution.py | 2 +- pytket/pytket/wasm/__init__.py | 1 + pytket/setup.py | 1 + pytket/tests/classical_test.py | 25 + pytket/tests/clexpr_test.py | 219 ++ pytket/tests/passes_serialisation_test.py | 10 +- pytket/tests/predicates_test.py | 52 +- pytket/tests/qasm_test.py | 20 +- .../tests/qasm_test_files/test17_output.qasm | 10 +- .../tests/qasm_test_files/test18_output.qasm | 16 +- schemas/compiler_pass_v1.json | 24 +- tket/CMakeLists.txt | 4 + tket/conanfile.py | 2 +- tket/include/tket/OpType/OpType.hpp | 7 +- tket/include/tket/OpType/OpTypeFunctions.hpp | 2 +- tket/include/tket/Ops/ClExpr.hpp | 306 +++ .../tket/Predicates/PassGenerators.hpp | 9 +- .../GreedyPauliOptimisation.hpp | 574 ++++- .../GreedyPauliOptimisationLookupTables.hpp | 2032 +++++------------ tket/src/Circuit/OpJson.cpp | 3 + tket/src/Circuit/basic_circ_manip.cpp | 2 +- tket/src/OpType/OpTypeFunctions.cpp | 2 +- tket/src/OpType/OpTypeInfo.cpp | 4 +- tket/src/Ops/ClExpr.cpp | 445 ++++ tket/src/Predicates/CompilerPass.cpp | 14 +- tket/src/Predicates/PassGenerators.cpp | 61 +- .../Transformations/GreedyPauliConverters.cpp | 524 +++++ tket/src/Transformations/GreedyPauliOps.cpp | 422 ++++ .../GreedyPauliOptimisation.cpp | 1172 ++++------ .../src/Transformations/PauliOptimisation.cpp | 9 +- tket/test/CMakeLists.txt | 1 + tket/test/src/test_ClExpr.cpp | 217 ++ tket/test/src/test_GreedyPauli.cpp | 484 +++- tket/test/src/test_PauliGraph.cpp | 9 + 71 files changed, 5651 insertions(+), 2466 deletions(-) create mode 100644 conan-profiles/macos-13 create mode 100644 conan-profiles/macos-15 create mode 100644 pytket/binders/circuit/clexpr.cpp create mode 100644 pytket/pytket/circuit/clexpr.py create mode 100644 pytket/tests/clexpr_test.py create mode 100644 tket/include/tket/Ops/ClExpr.hpp create mode 100644 tket/src/Ops/ClExpr.cpp create mode 100644 tket/src/Transformations/GreedyPauliConverters.cpp create mode 100644 tket/src/Transformations/GreedyPauliOps.cpp create mode 100644 tket/test/src/test_ClExpr.cpp diff --git a/.github/workflows/build-with-nix.yml b/.github/workflows/build-with-nix.yml index 09f33f5e41..34efc1dee8 100644 --- a/.github/workflows/build-with-nix.yml +++ b/.github/workflows/build-with-nix.yml @@ -16,11 +16,11 @@ jobs: build_and_test: strategy: matrix: - os: ['ubuntu-24.04', 'macos-14'] + os: ['ubuntu-24.04', 'macos-15'] runs-on: ${{matrix.os}} steps: - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v29 + - uses: cachix/install-nix-action@v30 - uses: cachix/cachix-action@v15 with: name: tket diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 7db90a3cce..5ada774af1 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -82,7 +82,7 @@ jobs: name: Check C++ code formatting needs: check_changes if: needs.check_changes.outputs.tket_or_workflow_changed == 'true' - runs-on: 'macos-14' + runs-on: 'macos-15' steps: - uses: actions/checkout@v4 - name: Check C++ code formatting @@ -98,7 +98,7 @@ jobs: if: needs.check_changes.outputs.tket_or_workflow_changed == 'true' strategy: matrix: - os: ['ubuntu-24.04', 'macos-12', 'macos-14'] + os: ['ubuntu-24.04', 'macos-13', 'macos-15'] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -286,11 +286,15 @@ jobs: if: github.event_name == 'schedule' uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - name: Build pytket run: | cd pytket - pip install -e .[ZX] -v + if python --version | grep -q '3.13' ; then + pip install -e . -v + else + pip install -e .[ZX] -v + fi - name: Run doctests run: | cd pytket @@ -341,7 +345,7 @@ jobs: if: needs.check_changes.outputs.tket_or_workflow_changed == 'true' || needs.check_changes.outputs.pytket_or_workflow_changed == 'true' strategy: matrix: - os: ['macos-12', 'macos-14'] + os: ['macos-13', 'macos-15'] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -362,7 +366,7 @@ jobs: - name: ccache uses: hendrikmuhs/ccache-action@v1.2 with: - key: tket-dynamic-macos-12 + key: tket-dynamic-macos - name: further ccache config run: | ccache --set-config base_dir=${HOME} @@ -399,11 +403,15 @@ jobs: if: github.event_name == 'schedule' uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - name: Build pytket run: | cd pytket - python -m pip install -e .[ZX] -v + if python --version | grep -q '3.13' ; then + pip install -e . -v + else + pip install -e .[ZX] -v + fi - name: Run doctests run: | cd pytket @@ -415,7 +423,7 @@ jobs: python -m pip install -r requirements.txt python -m pytest --ignore=simulator/ - name: Check type stubs are up-to-date and run mypy - if: matrix.os == 'macos-14' && (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') + if: matrix.os == 'macos-15' && (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') run: | python -m pip install -U mypy pybind11-stubgen cd pytket @@ -527,11 +535,16 @@ jobs: if: github.event_name == 'schedule' uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - name: Build pytket + shell: bash run: | cd pytket - pip install -e .[ZX] -v + if python --version | grep -q '3.13' ; then + pip install -e . -v + else + pip install -e .[ZX] -v + fi - name: Run doctests run: | cd pytket diff --git a/.github/workflows/build_libs.yml b/.github/workflows/build_libs.yml index af717d470b..8e61648959 100644 --- a/.github/workflows/build_libs.yml +++ b/.github/workflows/build_libs.yml @@ -46,7 +46,7 @@ jobs: if: ${{ needs.changes.outputs.libs != '[]' && needs.changes.outputs.libs != '' }} strategy: matrix: - os: ['ubuntu-24.04', 'macos-12', 'windows-2022', 'macos-14'] + os: ['ubuntu-24.04', 'macos-13', 'macos-15', 'windows-2022'] lib: ${{ fromJson(needs.changes.outputs.libs) }} build_type: ['Release', 'Debug'] runs-on: ${{ matrix.os }} diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 9a5980aee1..62756a5d4c 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -17,7 +17,7 @@ jobs: name: Build strategy: matrix: - os: ['ubuntu-24.04', 'macos-12', 'windows-2022', 'macos-14'] + os: ['ubuntu-24.04', 'macos-13', 'macos-14', 'macos-15', 'windows-2022'] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/pytket_benchmarking.yml b/.github/workflows/pytket_benchmarking.yml index 7d1d8a8c06..001418b4ac 100644 --- a/.github/workflows/pytket_benchmarking.yml +++ b/.github/workflows/pytket_benchmarking.yml @@ -30,7 +30,7 @@ jobs: run: | conan profile detect DEFAULT_PROFILE_PATH=`conan profile path default` - PROFILE_PATH=./conan-profiles/macos-14 + PROFILE_PATH=./conan-profiles/macos-15 diff ${DEFAULT_PROFILE_PATH} ${PROFILE_PATH} || true cp ${PROFILE_PATH} ${DEFAULT_PROFILE_PATH} conan remote add tket-libs https://quantinuumsw.jfrog.io/artifactory/api/conan/tket1-libs --index 0 @@ -57,7 +57,7 @@ jobs: compile-and-compare: name: Compile and compare - runs-on: macos-14 + runs-on: macos-15 needs: build_wheels steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4f95dadb82..2a675b349e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - python3-version: ['10', '11', '12'] + python3-version: ['10', '11', '12', '13'] steps: - uses: actions/checkout@v4 with: @@ -41,7 +41,7 @@ jobs: runs-on: 'buildjet-8vcpu-ubuntu-2204-arm' strategy: matrix: - python3-version: ['10', '11', '12'] + python3-version: ['10', '11', '12', '13'] steps: - uses: actions/checkout@v4 with: @@ -64,10 +64,10 @@ jobs: build_macos_x86_wheels: name: Build macos x86 wheels - runs-on: macos-12 + runs-on: macos-13 strategy: matrix: - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v4 with: @@ -83,7 +83,7 @@ jobs: run: | conan profile detect DEFAULT_PROFILE_PATH=`conan profile path default` - PROFILE_PATH=./conan-profiles/macos-12 + PROFILE_PATH=./conan-profiles/macos-13 diff ${DEFAULT_PROFILE_PATH} ${PROFILE_PATH} || true cp ${PROFILE_PATH} ${DEFAULT_PROFILE_PATH} conan remote add tket-libs https://quantinuumsw.jfrog.io/artifactory/api/conan/tket1-libs --index 0 @@ -94,8 +94,8 @@ jobs: conan create recipes/pybind11 conan create recipes/pybind11_json/all --version 0.2.14 cd pytket - # Ensure wheels are compatible with MacOS 12.0 and later: - export WHEEL_PLAT_NAME=macosx_12_0_x86_64 + # Ensure wheels are compatible with MacOS 13.0 and later: + export WHEEL_PLAT_NAME=macosx_13_0_x86_64 pip install -U pip build delocate python -m build delocate-wheel -v -w "$GITHUB_WORKSPACE/wheelhouse/" "dist/pytket-"*".whl" @@ -106,12 +106,12 @@ jobs: build_macos_arm64_wheels: name: Build macos arm64 wheels - runs-on: macos-14 + runs-on: macos-15 env: - MACOSX_DEPLOYMENT_TARGET: '12.0' + MACOSX_DEPLOYMENT_TARGET: '13.0' strategy: matrix: - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v4 with: @@ -127,7 +127,7 @@ jobs: run: | conan profile detect DEFAULT_PROFILE_PATH=`conan profile path default` - PROFILE_PATH=./conan-profiles/macos-14 + PROFILE_PATH=./conan-profiles/macos-15 diff ${DEFAULT_PROFILE_PATH} ${PROFILE_PATH} || true cp ${PROFILE_PATH} ${DEFAULT_PROFILE_PATH} conan remote add tket-libs https://quantinuumsw.jfrog.io/artifactory/api/conan/tket1-libs --index 0 @@ -138,8 +138,8 @@ jobs: conan create recipes/pybind11 conan create recipes/pybind11_json/all --version 0.2.14 cd pytket - # Ensure wheels are compatible with MacOS 12.0 and later: - export WHEEL_PLAT_NAME=macosx_12_0_arm64 + # Ensure wheels are compatible with MacOS 13.0 and later: + export WHEEL_PLAT_NAME=macosx_13_0_arm64 python${{ matrix.python-version }} -m pip install -U pip build delocate python${{ matrix.python-version }} -m build delocate-wheel -v -w "$GITHUB_WORKSPACE/wheelhouse/" "dist/pytket-"*".whl" @@ -153,7 +153,7 @@ jobs: runs-on: windows-2022 strategy: matrix: - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v4 with: @@ -194,7 +194,7 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - python3-version: ['10', '11', '12'] + python3-version: ['10', '11', '12', '13'] steps: - name: Set up Python 3.${{ matrix.python3-version }} uses: actions/setup-python@v5 @@ -223,18 +223,13 @@ jobs: runs-on: 'buildjet-4vcpu-ubuntu-2204-arm' strategy: matrix: - python3-version: ['10', '11', '12'] + python3-version: ['10', '11', '12', '13'] steps: - uses: actions/checkout@v4 with: path: tket - name: Set up Python 3.${{ matrix.python3-version }} - # Use deadsnakes/action because Python for Linux/ARM is not available with - # actions/setup-python. - # Python 3.10 is the default python on Ubuntu 22.04, so this combination - # is not available from deadsnakes: use the default python in this case. - if: matrix.python3-version != '10' - uses: deadsnakes/action@v3.2.0 + uses: actions/setup-python@v5 with: python-version: "3.${{ matrix.python3-version }}" - name: Download wheel @@ -259,8 +254,8 @@ jobs: needs: build_macos_x86_wheels strategy: matrix: - os: ['macos-12', 'macos-13'] - python-version: ['3.10', '3.11', '3.12'] + os: ['macos-13'] + python-version: ['3.10', '3.11', '3.12', '3.13'] runs-on: ${{ matrix.os }} steps: - name: Set up Python ${{ matrix.python-version }} @@ -289,8 +284,8 @@ jobs: needs: build_macos_arm64_wheels strategy: matrix: - os: ['macos-13-xlarge', 'macos-14'] - python-version: ['3.10', '3.11', '3.12'] + os: ['macos-14', 'macos-15'] + python-version: ['3.10', '3.11', '3.12', '3.13'] runs-on: ${{ matrix.os }} steps: - name: Set up Python ${{ matrix.python-version }} @@ -320,7 +315,7 @@ jobs: runs-on: windows-2022 strategy: matrix: - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.10', '3.11', '3.12', '3.13'] steps: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 diff --git a/.github/workflows/test_libs.yml b/.github/workflows/test_libs.yml index d8caee4c07..88a7d4ff3b 100644 --- a/.github/workflows/test_libs.yml +++ b/.github/workflows/test_libs.yml @@ -63,7 +63,7 @@ jobs: strategy: fail-fast: false matrix: - os: ['ubuntu-24.04', 'macos-12', 'windows-2022', 'macos-14'] + os: ['ubuntu-24.04', 'macos-13', 'macos-15', 'windows-2022'] lib: ${{ fromJson(needs.set_libs_matrix.outputs.libs) }} runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/test_libs_all.yml b/.github/workflows/test_libs_all.yml index a66e28f5b6..374604a46b 100644 --- a/.github/workflows/test_libs_all.yml +++ b/.github/workflows/test_libs_all.yml @@ -10,7 +10,7 @@ jobs: name: test library strategy: matrix: - os: ['ubuntu-24.04', 'macos-12', 'windows-2022', 'macos-14'] + os: ['ubuntu-24.04', 'macos-13', 'macos-15', 'windows-2022'] lib: ['tklog', 'tkassert', 'tkrng', 'tktokenswap', 'tkwsm'] runs-on: ${{ matrix.os }} steps: diff --git a/README.md b/README.md index ef9ab37e1a..da3d39cb72 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,9 @@ depend on the features they support. The compiler version can be controlled by setting `CC` and `CXX` in your environment (e.g. `CC=gcc-11` and `CXX=g++-11`), or on Debian-based Linux systems using `update-alternatives`. -You should also have Python (3.10, 3.11 or 3.12) and `pip` installed. We use -`cmake` and the package manager `conan` to build tket and pytket. The latter can -be installed with `pip`: +You should also have Python (3.10, 3.11, 3.12 or 3.13) and `pip` installed. We +use `cmake` and the package manager `conan` to build tket and pytket. The latter +can be installed with `pip`: ```shell pip install conan diff --git a/conan-profiles/macos-13 b/conan-profiles/macos-13 new file mode 100644 index 0000000000..c98940af1c --- /dev/null +++ b/conan-profiles/macos-13 @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=apple-clang +compiler.cppstd=gnu17 +compiler.libcxx=libc++ +compiler.version=15 +os=Macos diff --git a/conan-profiles/macos-15 b/conan-profiles/macos-15 new file mode 100644 index 0000000000..9368700dc6 --- /dev/null +++ b/conan-profiles/macos-15 @@ -0,0 +1,8 @@ +[settings] +arch=armv8 +build_type=Release +compiler=apple-clang +compiler.cppstd=gnu17 +compiler.libcxx=libc++ +compiler.version=16 +os=Macos diff --git a/flake.lock b/flake.lock index dacae6f678..ee935cf5ee 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1721812269, - "narHash": "sha256-qcI00YJBrLMmyPktlTS0UUvX/qGA7tVp73K6lJpRdy4=", + "lastModified": 1729693369, + "narHash": "sha256-45rettjkIaKhKLEK3KDYaxWIJstCd6G+W8idXXu3+YM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "5c31ee6b23a06da40bf2dfc9017a60308a4adc53", + "rev": "9a79bc998d0207268c8e00b737e896e2e717ccd2", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 48722d5c69..fc56792cb8 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,7 @@ (import ./nix-support/symengine.nix) (import ./nix-support/tket.nix) (import ./nix-support/third-party-python-packages.nix) - (import ./nix-support/pytket.nix { package_version = "1.33.0"; }) + (import ./nix-support/pytket.nix { package_version = "1.34.0"; }) ]; }; in { diff --git a/nix-support/pytket.nix b/nix-support/pytket.nix index 37a3abee9e..3d2a4a2ed7 100644 --- a/nix-support/pytket.nix +++ b/nix-support/pytket.nix @@ -73,7 +73,7 @@ in { } $out/lib/python${super.python3.pythonVersion}/site-packages/pytket/circuit/display/static; ''; checkInputs = with super.python3.pkgs; [ - mypy + self.mypy' pytest pytest-cov pytest-benchmark diff --git a/nix-support/third-party-python-packages.nix b/nix-support/third-party-python-packages.nix index 34eac8d362..7027ed4b18 100644 --- a/nix-support/third-party-python-packages.nix +++ b/nix-support/third-party-python-packages.nix @@ -1,4 +1,14 @@ self: super: { + mypy' = super.python3Packages.mypy.overrideAttrs (oldAttrs: rec { + version = "1.13.0"; + name = "python${super.python3.pythonVersion}-mypy-${version}"; + src = super.fetchFromGitHub { + owner = "python"; + repo = "mypy"; + rev = "refs/tags/v${version}"; + hash = sha256:P2Ozmj7/7QBmjlveHLsNdYgUAerg0qOoa8pO0iQc5os=; + }; + }); pybind11_json = super.stdenv.mkDerivation { name = "pybind11_json"; src = super.fetchFromGitHub { diff --git a/pytket/CMakeLists.txt b/pytket/CMakeLists.txt index a99d09ef13..4c58fc2a6c 100644 --- a/pytket/CMakeLists.txt +++ b/pytket/CMakeLists.txt @@ -113,6 +113,7 @@ pybind11_add_module(circuit binders/circuit/Circuit/add_op.cpp binders/circuit/Circuit/main.cpp binders/circuit/classical.cpp + binders/circuit/clexpr.cpp binders/circuit/main.cpp ${HEADER_FILES}) target_include_directories(circuit PRIVATE binders/include) diff --git a/pytket/binders/circuit/Circuit/add_op.cpp b/pytket/binders/circuit/Circuit/add_op.cpp index 20447d429d..c8c952d2f3 100644 --- a/pytket/binders/circuit/Circuit/add_op.cpp +++ b/pytket/binders/circuit/Circuit/add_op.cpp @@ -13,8 +13,10 @@ // limitations under the License. #include +#include #include +#include #include #include @@ -33,6 +35,7 @@ #include "tket/Circuit/ToffoliBox.hpp" #include "tket/Converters/PhasePoly.hpp" #include "tket/Gate/OpPtrFunctions.hpp" +#include "tket/Ops/ClExpr.hpp" #include "tket/Utils/UnitID.hpp" #include "typecast.hpp" namespace py = pybind11; @@ -483,6 +486,19 @@ void init_circuit_add_op(py::class_> &c) { ":param args: Indices of the qubits to append the box to" "\n:return: the new :py:class:`Circuit`", py::arg("expression"), py::arg("target")) + .def( + "add_clexpr", + [](Circuit *circ, const WiredClExpr &expr, + const py::tket_custom::SequenceVec &args, + const py::kwargs &kwargs) { + Op_ptr op = std::make_shared(expr); + return add_gate_method(circ, op, args, kwargs); + }, + "Append a :py:class:`WiredClExpr` to the circuit.\n\n" + ":param expr: The expression to append\n" + ":param args: The bits to apply the expression to\n" + ":return: the new :py:class:`Circuit`", + py::arg("expr"), py::arg("args")) .def( "add_custom_gate", [](Circuit *circ, const composite_def_ptr_t &definition, diff --git a/pytket/binders/circuit/clexpr.cpp b/pytket/binders/circuit/clexpr.cpp new file mode 100644 index 0000000000..f45559f585 --- /dev/null +++ b/pytket/binders/circuit/clexpr.cpp @@ -0,0 +1,554 @@ +// Copyright 2019-2024 Cambridge Quantum Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tket/Ops/ClExpr.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#include "UnitRegister.hpp" +#include "binder_json.hpp" +#include "deleted_hash.hpp" +#include "py_operators.hpp" + +namespace py = pybind11; + +namespace tket { + +static std::string qasm_bit_repr( + const ClExprTerm &term, const std::map &input_bits) { + if (const int *n = std::get_if(&term)) { + switch (*n) { + case 0: + return "0"; + case 1: + return "1"; + default: + throw std::logic_error("Invalid integer in bit operation"); + } + } else { + ClExprVar var = std::get(term); + if (const ClBitVar *bvar = std::get_if(&var)) { + const Bit b = input_bits.at(bvar->index); + return b.repr(); + } else { + throw std::logic_error("Expected bit variable, found register variable"); + } + } +} + +static std::string qasm_reg_repr( + const ClExprTerm &term, const std::map &input_regs) { + if (const int *n = std::get_if(&term)) { + std::stringstream ss; + ss << *n; + return ss.str(); + } else { + ClExprVar var = std::get(term); + if (const ClRegVar *rvar = std::get_if(&var)) { + const BitRegister r = input_regs.at(rvar->index); + return r.name(); + } else { + throw std::logic_error("Expected register variable, found bit variable"); + } + } +} + +enum class ArgValueType { Bit, Reg }; + +static std::string qasm_expr_repr( + const ClExpr &expr, const std::map &input_bits, + const std::map &input_regs); + +static std::string qasm_arg_repr( + const ClExprArg &arg, const std::map &input_bits, + const std::map &input_regs, const ArgValueType typ) { + if (const ClExpr *expr = std::get_if(&arg)) { + return qasm_expr_repr(*expr, input_bits, input_regs); + } else { + if (typ == ArgValueType::Bit) { + return qasm_bit_repr(std::get(arg), input_bits); + } else { + return qasm_reg_repr(std::get(arg), input_regs); + } + } +} + +static std::string qasm_expr_repr( + const ClExpr &expr, const std::map &input_bits, + const std::map &input_regs) { + const ClOp op = expr.get_op(); + const std::vector args = expr.get_args(); + const unsigned n_args = args.size(); + std::stringstream ss; + ss << "("; + switch (op) { + case ClOp::INVALID: + throw std::logic_error("Invalid expression."); + + case ClOp::BitAnd: + if (n_args == 0) { + ss << "1"; + } else { + for (unsigned i = 0; i < n_args; i++) { + ss << qasm_arg_repr( + args[i], input_bits, input_regs, ArgValueType::Bit); + if (i + 1 < n_args) { + ss << " & "; + } + } + } + break; + + case ClOp::BitOr: + if (n_args == 0) { + ss << "0"; + } else { + for (unsigned i = 0; i < n_args; i++) { + ss << qasm_arg_repr( + args[i], input_bits, input_regs, ArgValueType::Bit); + if (i + 1 < n_args) { + ss << " | "; + } + } + } + break; + + case ClOp::BitXor: + if (n_args == 0) { + ss << "0"; + } else { + for (unsigned i = 0; i < n_args; i++) { + ss << qasm_arg_repr( + args[i], input_bits, input_regs, ArgValueType::Bit); + if (i + 1 < n_args) { + ss << " ^ "; + } + } + } + break; + + case ClOp::BitEq: + if (n_args != 2) { + throw std::logic_error("BitEq with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Bit); + ss << " == "; + ss << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Bit); + break; + + case ClOp::BitNeq: + if (n_args != 2) { + throw std::logic_error("BitNeq with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Bit) + << " != " + << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Bit); + break; + + case ClOp::BitNot: + if (n_args != 1) { + throw std::logic_error("BitNot with != 1 argument"); + } + ss << "~" + << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Bit); + break; + + case ClOp::BitZero: + if (n_args != 0) { + throw std::logic_error("BitZero with != 0 arguments"); + } + ss << "0"; + break; + + case ClOp::BitOne: + if (n_args != 0) { + throw std::logic_error("BitOne with != 0 arguments"); + } + ss << "1"; + break; + + case ClOp::RegAnd: + if (n_args == 0) { + ss << "-1"; + } else { + for (unsigned i = 0; i < n_args; i++) { + ss << qasm_arg_repr( + args[i], input_bits, input_regs, ArgValueType::Reg); + if (i + 1 < n_args) { + ss << " & "; + } + } + } + break; + + case ClOp::RegOr: + if (n_args == 0) { + ss << "0"; + } else { + for (unsigned i = 0; i < n_args; i++) { + ss << qasm_arg_repr( + args[i], input_bits, input_regs, ArgValueType::Reg); + if (i + 1 < n_args) { + ss << " | "; + } + } + } + break; + + case ClOp::RegXor: + if (n_args == 0) { + ss << "0"; + } else { + for (unsigned i = 0; i < n_args; i++) { + ss << qasm_arg_repr( + args[i], input_bits, input_regs, ArgValueType::Reg); + if (i + 1 < n_args) { + ss << " ^ "; + } + } + } + break; + + case ClOp::RegEq: + if (n_args != 2) { + throw std::logic_error("RegEq with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg); + ss << " == "; + ss << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Reg); + break; + + case ClOp::RegNeq: + if (n_args != 2) { + throw std::logic_error("RegNeq with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg) + << " != " + << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Reg); + break; + + case ClOp::RegNot: + if (n_args != 1) { + throw std::logic_error("RegNot with != 1 argument"); + } + ss << "~" + << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg); + break; + + case ClOp::RegZero: + if (n_args != 0) { + throw std::logic_error("RegZero with != 0 arguments"); + } + ss << "0"; + break; + + case ClOp::RegOne: + if (n_args != 0) { + throw std::logic_error("RegOne with != 0 arguments"); + } + ss << "-1"; + break; + + case ClOp::RegLt: + if (n_args != 2) { + throw std::logic_error("RegLt with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg); + ss << " < "; + ss << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Reg); + break; + + case ClOp::RegGt: + if (n_args != 2) { + throw std::logic_error("RegGt with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg); + ss << " > "; + ss << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Reg); + break; + + case ClOp::RegLeq: + if (n_args != 2) { + throw std::logic_error("RegLeq with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg); + ss << " <= "; + ss << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Reg); + break; + + case ClOp::RegGeq: + if (n_args != 2) { + throw std::logic_error("RegGeq with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg); + ss << " >= "; + ss << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Reg); + break; + + case ClOp::RegAdd: + if (n_args == 0) { + ss << "0"; + } else { + for (unsigned i = 0; i < n_args; i++) { + ss << qasm_arg_repr( + args[i], input_bits, input_regs, ArgValueType::Reg); + if (i + 1 < n_args) { + ss << " + "; + } + } + } + break; + + case ClOp::RegSub: + if (n_args != 2) { + throw std::logic_error("RegSub with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg); + ss << " - "; + ss << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Reg); + break; + + case ClOp::RegMul: + if (n_args == 0) { + ss << "1"; + } else { + for (unsigned i = 0; i < n_args; i++) { + ss << qasm_arg_repr( + args[i], input_bits, input_regs, ArgValueType::Reg); + if (i + 1 < n_args) { + ss << " * "; + } + } + } + break; + + case ClOp::RegDiv: + if (n_args != 2) { + throw std::logic_error("RegDiv with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg); + ss << " / "; + ss << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Reg); + break; + + case ClOp::RegPow: + if (n_args != 2) { + throw std::logic_error("RegPow with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg); + ss << " ** "; + ss << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Reg); + break; + + case ClOp::RegLsh: + if (n_args != 2) { + throw std::logic_error("RegLsh with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg); + ss << " << "; + ss << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Reg); + break; + + case ClOp::RegRsh: + if (n_args != 2) { + throw std::logic_error("RegRsh with != 2 arguments"); + } + ss << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg); + ss << " >> "; + ss << qasm_arg_repr(args[1], input_bits, input_regs, ArgValueType::Reg); + break; + + case ClOp::RegNeg: + if (n_args != 1) { + throw std::logic_error("RegNeg with != 1 argument"); + } + ss << "-" + << qasm_arg_repr(args[0], input_bits, input_regs, ArgValueType::Reg); + break; + } + ss << ")"; + return ss.str(); +} + +void init_clexpr(py::module &m) { + py::enum_(m, "ClOp", "A classical operation", py::arithmetic()) + .value("INVALID", ClOp::INVALID, "Invalid") + .value("BitAnd", ClOp::BitAnd, "Bitwise AND") + .value("BitOr", ClOp::BitOr, "Bitwise OR") + .value("BitXor", ClOp::BitXor, "Bitwise XOR") + .value("BitEq", ClOp::BitEq, "Bitwise equality") + .value("BitNeq", ClOp::BitNeq, "Bitwise inequality") + .value("BitNot", ClOp::BitNot, "Bitwise NOT") + .value("BitZero", ClOp::BitZero, "Constant zero bit") + .value("BitOne", ClOp::BitOne, "Constant one bit") + .value("RegAnd", ClOp::RegAnd, "Registerwise AND") + .value("RegOr", ClOp::RegOr, "Registerwise OR") + .value("RegXor", ClOp::RegXor, "Registerwise XOR") + .value("RegEq", ClOp::RegEq, "Registerwise equality") + .value("RegNeq", ClOp::RegNeq, "Registerwise inequality") + .value("RegNot", ClOp::RegNot, "Registerwise NOT") + .value("RegZero", ClOp::RegZero, "Constant all-zeros register") + .value("RegOne", ClOp::RegOne, "Constant all-ones register") + .value("RegLt", ClOp::RegLt, "Integer less-than comparison") + .value("RegGt", ClOp::RegGt, "Integer greater-than comparison") + .value("RegLeq", ClOp::RegLeq, "Integer less-than-or-equal comparison") + .value("RegGeq", ClOp::RegGeq, "Integer greater-than-or-equal comparison") + .value("RegAdd", ClOp::RegAdd, "Integer addition") + .value("RegSub", ClOp::RegSub, "Integer subtraction") + .value("RegMul", ClOp::RegMul, "Integer multiplication") + .value("RegDiv", ClOp::RegDiv, "Integer division") + .value("RegPow", ClOp::RegPow, "Integer exponentiation") + .value("RegLsh", ClOp::RegLsh, "Left shift") + .value("RegRsh", ClOp::RegRsh, "Right shift") + .value("RegNeg", ClOp::RegNeg, "Integer negation"); + + py::class_>( + m, "ClBitVar", "A bit variable within an expression") + .def( + py::init(), "Construct from an integer identifier", + py::arg("i")) + .def("__eq__", &py_equals) + .def( + "__str__", + [](const ClBitVar &var) { + std::stringstream ss; + ss << var; + return ss.str(); + }) + .def( + "__repr__", + [](const ClBitVar &var) { + std::stringstream ss; + ss << "ClBitVar(" << var.index << ")"; + return ss.str(); + }) + .def("__hash__", [](const ClBitVar &var) { return var.index; }) + .def_property_readonly( + "index", [](const ClBitVar &var) { return var.index; }, + ":return: integer identifier for the variable"); + + py::class_>( + m, "ClRegVar", "A register variable within an expression") + .def( + py::init(), "Construct from an integer identifier", + py::arg("i")) + .def("__eq__", &py_equals) + .def( + "__str__", + [](const ClRegVar &var) { + std::stringstream ss; + ss << var; + return ss.str(); + }) + .def( + "__repr__", + [](const ClRegVar &var) { + std::stringstream ss; + ss << "ClRegVar(" << var.index << ")"; + return ss.str(); + }) + .def("__hash__", [](const ClRegVar &var) { return var.index; }) + .def_property_readonly( + "index", [](const ClRegVar &var) { return var.index; }, + ":return: integer identifier for the variable"); + + py::class_>( + m, "ClExpr", "A classical expression") + .def( + py::init>(), + "Construct from an operation and a list of arguments", py::arg("op"), + py::arg("args")) + .def("__eq__", &py_equals) + .def( + "__str__", + [](const ClExpr &expr) { + std::stringstream ss; + ss << expr; + return ss.str(); + }) + .def("__hash__", &deletedHash, deletedHashDocstring) + .def_property_readonly("op", &ClExpr::get_op, ":return: main operation") + .def_property_readonly("args", &ClExpr::get_args, ":return: arguments") + .def( + "as_qasm", + [](const ClExpr &expr, const std::map input_bits, + const std::map input_regs) -> std::string { + return qasm_expr_repr(expr, input_bits, input_regs); + }, + "QASM-style string representation given corresponding bits and " + "registers", + py::arg("input_bits"), py::arg("input_regs")); + + py::class_>( + m, "WiredClExpr", + "A classical expression defined over a sequence of bits") + .def( + py::init< + ClExpr, std::map, + std::map>, + std::vector>(), + "Construct from an expression with bit and register positions", + py::arg("expr"), py::arg("bit_posn") = std::map(), + py::arg("reg_posn") = std::map>(), + py::arg("output_posn") = std::vector()) + .def("__eq__", &py_equals) + .def( + "__str__", + [](const WiredClExpr &expr) { + std::stringstream ss; + ss << expr; + return ss.str(); + }) + .def("__hash__", &deletedHash, deletedHashDocstring) + .def_property_readonly( + "expr", &WiredClExpr::get_expr, ":return: expression") + .def_property_readonly( + "bit_posn", &WiredClExpr::get_bit_posn, ":return: bit positions") + .def_property_readonly( + "reg_posn", &WiredClExpr::get_reg_posn, ":return: register positions") + .def_property_readonly( + "output_posn", &WiredClExpr::get_output_posn, + ":return: output positions") + .def( + "to_dict", + [](const WiredClExpr &wexpr) { + return py::object(nlohmann::json(wexpr)).cast(); + }, + ":return: JSON-serializable dict representation") + .def_static( + "from_dict", + [](const py::dict &wexpr_dict) { + return nlohmann::json(wexpr_dict).get(); + }, + "Construct from JSON-serializable dict representation"); + + py::class_, Op>( + m, "ClExprOp", "An operation defined by a classical expression") + .def( + py::init(), + "Construct from a wired classical expression") + .def_property_readonly( + "type", &ClExprOp::get_type, ":return: operation type") + .def_property_readonly( + "expr", &ClExprOp::get_wired_expr, ":return: wired expression"); +} + +} // namespace tket diff --git a/pytket/binders/circuit/main.cpp b/pytket/binders/circuit/main.cpp index 4ada6280bf..ba4d2853f3 100644 --- a/pytket/binders/circuit/main.cpp +++ b/pytket/binders/circuit/main.cpp @@ -25,6 +25,7 @@ #include "tket/Circuit/Command.hpp" #include "tket/Gate/OpPtrFunctions.hpp" #include "tket/Gate/SymTable.hpp" +#include "tket/OpType/OpType.hpp" #include "tket/Ops/BarrierOp.hpp" #include "tket/Ops/MetaOp.hpp" #include "tket/Ops/Op.hpp" @@ -42,6 +43,7 @@ typedef py::tket_custom::SequenceVec py_unit_vector_t; void def_circuit(py::class_> &); void init_classical(py::module &m); void init_boxes(py::module &m); +void init_clexpr(py::module &m); PYBIND11_MODULE(circuit, m) { py::module::import("pytket._tket.unit_id"); @@ -545,6 +547,7 @@ PYBIND11_MODULE(circuit, m) { "DiagonalBox", OpType::DiagonalBox, "A box for synthesising a diagonal unitary matrix into a sequence of " "multiplexed-Rz gates") + .value("ClExpr", OpType::ClExpr, "A classical expression") .def_static( "from_name", [](const py::str &name) { return json(name).get(); }, @@ -688,6 +691,7 @@ PYBIND11_MODULE(circuit, m) { "result in bit 0"); init_boxes(m); init_classical(m); + init_clexpr(m); def_circuit(pyCircuit); m.def( diff --git a/pytket/binders/passes.cpp b/pytket/binders/passes.cpp index 48590f9c81..d47181072f 100644 --- a/pytket/binders/passes.cpp +++ b/pytket/binders/passes.cpp @@ -927,8 +927,19 @@ PYBIND11_MODULE(passes, m) { "\n\n:param discount_rate: Rate used to discount the cost impact from " "gadgets that are further away. Default to 0.7." "\n:param depth_weight: Degree of depth optimisation. Default to 0.3." + "\n:param max_tqe_candidates: Maximum number of 2-qubit Clifford " + "gate candidates to evaluate at each step. Default to 500." + "\n:param max_lookahead: Maximum lookahead when evaluating each " + "Clifford gate candidate. Default to 500." + "\n:param seed: Unsigned integer seed used for sampling candidates " + "and tie breaking. Default to 0." + "\n:param allow_zzphase: If set to True, allows the algorithm to " + "implement 2-qubit rotations using ZZPhase gates when deemed " + "optimal. Defaults to False." "\n:return: a pass to perform the simplification", - py::arg("discount_rate") = 0.7, py::arg("depth_weight") = 0.3); + py::arg("discount_rate") = 0.7, py::arg("depth_weight") = 0.3, + py::arg("max_lookahead") = 500, py::arg("max_tqe_candidates") = 500, + py::arg("seed") = 0, py::arg("allow_zzphase") = false); m.def( "PauliSquash", &PauliSquash, "Applies :py:meth:`PauliSimp` followed by " diff --git a/pytket/binders/transform.cpp b/pytket/binders/transform.cpp index 8ef8027b78..21724759c3 100644 --- a/pytket/binders/transform.cpp +++ b/pytket/binders/transform.cpp @@ -431,8 +431,19 @@ PYBIND11_MODULE(transform, m) { "gadgets that are further away. Default to 0.7." "\n:param depth_weight: Degree of depth optimisation. Default to " "0.3." + "\n:param max_tqe_candidates: Maximum number of 2-qubit Clifford " + "gate candidates to evaluate at each step. Default to 500." + "\n:param max_lookahead: Maximum lookahead when evaluating each " + "Clifford gate candidate. Default to 500." + "\n:param seed: Unsigned integer seed used for sampling candidates " + "and tie breaking. Default to 0." + "\n:param allow_zzphase: If set to True, allows the algorithm to " + "implement 2-qubit rotations using ZZPhase gates when deemed " + "optimal. Defaults to False." "\n:return: a pass to perform the simplification", - py::arg("discount_rate") = 0.7, py::arg("depth_weight") = 0.3) + py::arg("discount_rate") = 0.7, py::arg("depth_weight") = 0.3, + py::arg("max_tqe_candidates") = 500, py::arg("max_lookahead") = 500, + py::arg("seed") = 0, py::arg("allow_zzphase") = false) .def_static( "ZZPhaseToRz", &Transforms::ZZPhase_to_Rz, "Fixes all ZZPhase gate angles to [-1, 1) half turns.") diff --git a/pytket/conanfile.py b/pytket/conanfile.py index eb5c7adbac..80e7e0b31c 100644 --- a/pytket/conanfile.py +++ b/pytket/conanfile.py @@ -38,7 +38,7 @@ def requirements(self): self.requires("pybind11_json/0.2.14") self.requires("symengine/0.12.0") self.requires("tkassert/0.3.4@tket/stable") - self.requires("tket/1.3.31@tket/stable") + self.requires("tket/1.3.35@tket/stable") self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") self.requires("tktokenswap/0.3.9@tket/stable") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 5d35129f96..143d5592c8 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -1,6 +1,38 @@ Changelog ========= +1.34.0 (October 2024) +--------------------- + +Features: + +* Add new `ClExprOp` operation type as an alternative to `ClassicalExpBox`; add + option to use this when converting from QASM. +* Several updates to `GreedyPauliSimp`: + + * Support for mid-circuit measurements, resets, conditionals, and classical gates. + * New parameters `max_lookahead` and `max_tqe_candidates` are added + to limit the search space. + * New parameter `seed` is added to support random sampling and tie breaking. + * New parameter `allow_zzphase` allows the algorithm to implement 2-qubit rotations + using ZZPhase gates when deemed optimal. + +Fixes: + +* Fix small default display screen for circuit renderer. + +General: + +* Support Python 3.13. +* Drop support for MacOS 12. + +1.33.1 (October 2024) +--------------------- + +Fixes: + +* Fix `GuidedPauliSimp` for circuits containing `CircBox` with classical wires. + 1.33.0 (October 2024) --------------------- diff --git a/pytket/docs/display.md b/pytket/docs/display.md index e74f075494..2a4092c240 100644 --- a/pytket/docs/display.md +++ b/pytket/docs/display.md @@ -29,11 +29,12 @@ from pytket.circuit.display import get_circuit_renderer circuit_renderer = get_circuit_renderer() # Instantiate a circuit renderer circuit_renderer.set_render_options(zx_style=True) # Configure render options -circuit_renderer.condense_c_bits = False # You can also set the properties on the instance directly +circuit_renderer.config.render_options.condense_c_bits = False # You can also set the properties on the instance directly +circuit_renderer.config.min_height = "300px" # Change the display height print("Render options:") print(circuit_renderer.get_render_options()) # View currently set render options -circuit_renderer.min_height = "300px" # Change the display height +circuit_renderer.save_render_options() # Export current render options to the pytket config circ = Circuit(2,2) # Define Circuit circ.H(0).H(1).CX(0, 1).Rz(0.4, 1).CX(0, 1).H(0).H(1).measure_all() diff --git a/pytket/docs/getting_started.rst b/pytket/docs/getting_started.rst index e123a904a6..17fece3741 100644 --- a/pytket/docs/getting_started.rst +++ b/pytket/docs/getting_started.rst @@ -7,8 +7,8 @@ NISQ (noisy intermediate-scale quantum) devices. The pytket package provides an API for interacting with tket and transpiling to and from other popular quantum circuit specifications. -Pytket is compatible with 64-bit Python 3.10, 3.11 and 3.12, on Linux, MacOS -(12.0 or later) and Windows. Install pytket from PyPI using: +Pytket is compatible with 64-bit Python 3.10, 3.11, 3.12 and 3.13, on Linux, +MacOS (13.0 or later) and Windows. Install pytket from PyPI using: :: diff --git a/pytket/docs/index.rst b/pytket/docs/index.rst index f8c40521d3..a11f899abc 100644 --- a/pytket/docs/index.rst +++ b/pytket/docs/index.rst @@ -5,8 +5,8 @@ pytket `numerous providers `_, allowing the tket tools to be used in conjunction with projects on their platforms. -``pytket`` is available for Python 3.10, 3.11 and 3.12, on Linux, MacOS and -Windows. To install, run +``pytket`` is available for Python 3.10, 3.11, 3.12 and 3.13, on Linux, MacOS +and Windows. To install, run :: diff --git a/pytket/docs/install.rst b/pytket/docs/install.rst index c5014b81c1..8aaafb32ea 100644 --- a/pytket/docs/install.rst +++ b/pytket/docs/install.rst @@ -36,7 +36,7 @@ Installation FAQs Is there a build of ``pytket`` for my system? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The core pytket package, as well as the separate extension modules are available on PyPI. Wheels are built to work on Linux, MacOS and Windows with Python versions 3.10, 3.11, or 3.12, and ``pip`` version 20.0.0+. +The core pytket package, as well as the separate extension modules are available on PyPI. Wheels are built to work on Linux, MacOS and Windows with Python versions 3.10, 3.11, 3.12 or 3.13, and ``pip`` version 20.0.0+. .. note:: On M1-based Macs running in native (arm64) mode, this command may fail @@ -70,7 +70,9 @@ At a couple of points in the development of the software, we had to increase the One possibility is that you are using an old version of ``pip`` that cannot accept the more recent Linux builds. Try running ``pip install --upgrade pip`` to upgrade it to the most recent version and upgrade ``pytket``. -As of pytket release 1.24.0 installing the latest version of pytket requires python version 3.10, 3.11 or 3.12. If you have an older version of python then you will need to upgrade it to use the latest version of pytket and the extensions. +As of pytket release 1.24.0 installing the latest version of pytket requires python version 3.10 or above. If you have an older version of python then you will need to upgrade it to use the latest version of pytket and the extensions. + +As of pytket release 1.34.0 MacOS 12 is no longer supported: please upgrade to MacOS 13 or above to use the latest version of pytket. There is a `known issue `_ with installing pytket in a conda environment on MacOS: you may not be able to diff --git a/pytket/package.md b/pytket/package.md index 3c01575718..2aca55d829 100644 --- a/pytket/package.md +++ b/pytket/package.md @@ -4,7 +4,7 @@ The source code for the TKET compiler can be found in [this github repository](h ## Installation -Installation is supported for Linux, MacOS and Windows. Installation requires python 3.10, 3.11 or 3.12. +Installation is supported for Linux, MacOS and Windows. Installation requires python 3.10, 3.11, 3.12 or 3.13. To install run the pip command: diff --git a/pytket/pytket/_tket/circuit.pyi b/pytket/pytket/_tket/circuit.pyi index d27b223203..b89daa701e 100644 --- a/pytket/pytket/_tket/circuit.pyi +++ b/pytket/pytket/_tket/circuit.pyi @@ -11,7 +11,7 @@ import pytket.circuit.logic_exp import pytket.wasm.wasm import sympy import typing -__all__ = ['BarrierOp', 'BasisOrder', 'CXConfigType', 'CircBox', 'Circuit', 'ClassicalEvalOp', 'ClassicalExpBox', 'ClassicalOp', 'Command', 'Conditional', 'ConjugationBox', 'CopyBitsOp', 'CustomGate', 'CustomGateDef', 'DiagonalBox', 'DummyBox', 'EdgeType', 'ExpBox', 'MetaOp', 'MultiBitOp', 'MultiplexedRotationBox', 'MultiplexedTensoredU2Box', 'MultiplexedU2Box', 'MultiplexorBox', 'Op', 'OpType', 'PauliExpBox', 'PauliExpCommutingSetBox', 'PauliExpPairBox', 'PhasePolyBox', 'ProjectorAssertionBox', 'QControlBox', 'RangePredicateOp', 'ResourceBounds', 'ResourceData', 'SetBitsOp', 'StabiliserAssertionBox', 'StatePreparationBox', 'TermSequenceBox', 'ToffoliBox', 'ToffoliBoxSynthStrat', 'Unitary1qBox', 'Unitary2qBox', 'Unitary3qBox', 'WASMOp', 'fresh_symbol'] +__all__ = ['BarrierOp', 'BasisOrder', 'CXConfigType', 'CircBox', 'Circuit', 'ClBitVar', 'ClExpr', 'ClExprOp', 'ClOp', 'ClRegVar', 'ClassicalEvalOp', 'ClassicalExpBox', 'ClassicalOp', 'Command', 'Conditional', 'ConjugationBox', 'CopyBitsOp', 'CustomGate', 'CustomGateDef', 'DiagonalBox', 'DummyBox', 'EdgeType', 'ExpBox', 'MetaOp', 'MultiBitOp', 'MultiplexedRotationBox', 'MultiplexedTensoredU2Box', 'MultiplexedU2Box', 'MultiplexorBox', 'Op', 'OpType', 'PauliExpBox', 'PauliExpCommutingSetBox', 'PauliExpPairBox', 'PhasePolyBox', 'ProjectorAssertionBox', 'QControlBox', 'RangePredicateOp', 'ResourceBounds', 'ResourceData', 'SetBitsOp', 'StabiliserAssertionBox', 'StatePreparationBox', 'TermSequenceBox', 'ToffoliBox', 'ToffoliBoxSynthStrat', 'Unitary1qBox', 'Unitary2qBox', 'Unitary3qBox', 'WASMOp', 'WiredClExpr', 'fresh_symbol'] class BarrierOp(Op): """ Barrier operations. @@ -1451,6 +1451,14 @@ class Circuit: :param args: Indices of the qubits to append the box to :return: the new :py:class:`Circuit` """ + def add_clexpr(self, expr: WiredClExpr, args: typing.Sequence[pytket._tket.unit_id.Bit], **kwargs: Any) -> Circuit: + """ + Append a :py:class:`WiredClExpr` to the circuit. + + :param expr: The expression to append + :param args: The bits to apply the expression to + :return: the new :py:class:`Circuit` + """ @typing.overload def add_conditional_barrier(self, barrier_qubits: typing.Sequence[int], barrier_bits: typing.Sequence[int], condition_bits: typing.Sequence[int], value: int, data: str = '') -> Circuit: """ @@ -1965,14 +1973,14 @@ class Circuit: :param qubit_2: index of target qubit 2 :return: the new :py:class:`Circuit` """ - def add_wasm(self, funcname: str, filehandler: pytket.wasm.wasm.WasmFileHandler, list_i: typing.Sequence[int], list_o: typing.Sequence[int], args: typing.Union[typing.Sequence[int], typing.Sequence[pytket._tket.unit_id.Bit]], args_wasm: typing.Optional[typing.Sequence[int]] = None, **kwargs: typing.Any) -> Circuit: + def add_wasm(self, funcname: str, filehandler: pytket.wasm.wasm.WasmModuleHandler, list_i: typing.Sequence[int], list_o: typing.Sequence[int], args: typing.Union[typing.Sequence[int], typing.Sequence[pytket._tket.unit_id.Bit]], args_wasm: typing.Optional[typing.Sequence[int]] = None, **kwargs: typing.Any) -> Circuit: """ Add a classical function call from a wasm file to the circuit. :param funcname: name of the function that is called - :param filehandler: wasm file handler to identify the wasm file + :param filehandler: wasm file or module handler to identify the wasm module :param list_i: list of the number of bits in the input variables @@ -1988,14 +1996,14 @@ class Circuit: :return: the new :py:class:`Circuit` """ - def add_wasm_to_reg(self, funcname: str, filehandler: pytket.wasm.wasm.WasmFileHandler, list_i: typing.Sequence[pytket._tket.unit_id.BitRegister], list_o: typing.Sequence[pytket._tket.unit_id.BitRegister], args_wasm: typing.Optional[typing.Sequence[int]] = None, **kwargs: typing.Any) -> Circuit: + def add_wasm_to_reg(self, funcname: str, filehandler: pytket.wasm.wasm.WasmModuleHandler, list_i: typing.Sequence[pytket._tket.unit_id.BitRegister], list_o: typing.Sequence[pytket._tket.unit_id.BitRegister], args_wasm: typing.Optional[typing.Sequence[int]] = None, **kwargs: typing.Any) -> Circuit: """ Add a classical function call from a wasm file to the circuit. :param funcname: name of the function that is called - :param filehandler: wasm file handler to identify the wasm file + :param filehandler: wasm file or module handler to identify the wasm module :param list_i: list of the classical registers assigned to the input variables of the function call @@ -2540,6 +2548,239 @@ class Circuit: """ A list of all qubit ids in the circuit """ +class ClBitVar: + """ + A bit variable within an expression + """ + @staticmethod + def _pybind11_conduit_v1_(*args, **kwargs): # type: ignore + ... + def __eq__(self, arg0: typing.Any) -> bool: + ... + def __hash__(self) -> int: + ... + def __init__(self, i: int) -> None: + """ + Construct from an integer identifier + """ + def __repr__(self) -> str: + ... + def __str__(self) -> str: + ... + @property + def index(self) -> int: + """ + :return: integer identifier for the variable + """ +class ClExpr: + """ + A classical expression + """ + @staticmethod + def _pybind11_conduit_v1_(*args, **kwargs): # type: ignore + ... + def __eq__(self, arg0: typing.Any) -> bool: + ... + def __hash__(self) -> int: + """ + Hashing is not implemented for this class, attempting to hash an object will raise a type error + """ + def __init__(self, op: ClOp, args: list[int | ClBitVar | ClRegVar | ClExpr]) -> None: + """ + Construct from an operation and a list of arguments + """ + def __str__(self) -> str: + ... + def as_qasm(self, input_bits: dict[int, pytket._tket.unit_id.Bit], input_regs: dict[int, pytket._tket.unit_id.BitRegister]) -> str: + """ + QASM-style string representation given corresponding bits and registers + """ + @property + def args(self) -> list[int | ClBitVar | ClRegVar | ClExpr]: + """ + :return: arguments + """ + @property + def op(self) -> ClOp: + """ + :return: main operation + """ +class ClExprOp(Op): + """ + An operation defined by a classical expression + """ + @staticmethod + def _pybind11_conduit_v1_(*args, **kwargs): # type: ignore + ... + def __init__(self, arg0: WiredClExpr) -> None: + """ + Construct from a wired classical expression + """ + @property + def expr(self) -> WiredClExpr: + """ + :return: wired expression + """ + @property + def type(self) -> OpType: + """ + :return: operation type + """ +class ClOp: + """ + A classical operation + + Members: + + INVALID : Invalid + + BitAnd : Bitwise AND + + BitOr : Bitwise OR + + BitXor : Bitwise XOR + + BitEq : Bitwise equality + + BitNeq : Bitwise inequality + + BitNot : Bitwise NOT + + BitZero : Constant zero bit + + BitOne : Constant one bit + + RegAnd : Registerwise AND + + RegOr : Registerwise OR + + RegXor : Registerwise XOR + + RegEq : Registerwise equality + + RegNeq : Registerwise inequality + + RegNot : Registerwise NOT + + RegZero : Constant all-zeros register + + RegOne : Constant all-ones register + + RegLt : Integer less-than comparison + + RegGt : Integer greater-than comparison + + RegLeq : Integer less-than-or-equal comparison + + RegGeq : Integer greater-than-or-equal comparison + + RegAdd : Integer addition + + RegSub : Integer subtraction + + RegMul : Integer multiplication + + RegDiv : Integer division + + RegPow : Integer exponentiation + + RegLsh : Left shift + + RegRsh : Right shift + + RegNeg : Integer negation + """ + BitAnd: typing.ClassVar[ClOp] # value = + BitEq: typing.ClassVar[ClOp] # value = + BitNeq: typing.ClassVar[ClOp] # value = + BitNot: typing.ClassVar[ClOp] # value = + BitOne: typing.ClassVar[ClOp] # value = + BitOr: typing.ClassVar[ClOp] # value = + BitXor: typing.ClassVar[ClOp] # value = + BitZero: typing.ClassVar[ClOp] # value = + INVALID: typing.ClassVar[ClOp] # value = + RegAdd: typing.ClassVar[ClOp] # value = + RegAnd: typing.ClassVar[ClOp] # value = + RegDiv: typing.ClassVar[ClOp] # value = + RegEq: typing.ClassVar[ClOp] # value = + RegGeq: typing.ClassVar[ClOp] # value = + RegGt: typing.ClassVar[ClOp] # value = + RegLeq: typing.ClassVar[ClOp] # value = + RegLsh: typing.ClassVar[ClOp] # value = + RegLt: typing.ClassVar[ClOp] # value = + RegMul: typing.ClassVar[ClOp] # value = + RegNeg: typing.ClassVar[ClOp] # value = + RegNeq: typing.ClassVar[ClOp] # value = + RegNot: typing.ClassVar[ClOp] # value = + RegOne: typing.ClassVar[ClOp] # value = + RegOr: typing.ClassVar[ClOp] # value = + RegPow: typing.ClassVar[ClOp] # value = + RegRsh: typing.ClassVar[ClOp] # value = + RegSub: typing.ClassVar[ClOp] # value = + RegXor: typing.ClassVar[ClOp] # value = + RegZero: typing.ClassVar[ClOp] # value = + __members__: typing.ClassVar[dict[str, ClOp]] # value = {'INVALID': , 'BitAnd': , 'BitOr': , 'BitXor': , 'BitEq': , 'BitNeq': , 'BitNot': , 'BitZero': , 'BitOne': , 'RegAnd': , 'RegOr': , 'RegXor': , 'RegEq': , 'RegNeq': , 'RegNot': , 'RegZero': , 'RegOne': , 'RegLt': , 'RegGt': , 'RegLeq': , 'RegGeq': , 'RegAdd': , 'RegSub': , 'RegMul': , 'RegDiv': , 'RegPow': , 'RegLsh': , 'RegRsh': , 'RegNeg': } + @staticmethod + def _pybind11_conduit_v1_(*args, **kwargs): # type: ignore + ... + def __eq__(self, other: typing.Any) -> bool: + ... + def __ge__(self, other: typing.Any) -> bool: + ... + def __getstate__(self) -> int: + ... + def __gt__(self, other: typing.Any) -> bool: + ... + def __hash__(self) -> int: + ... + def __index__(self) -> int: + ... + def __init__(self, value: int) -> None: + ... + def __int__(self) -> int: + ... + def __le__(self, other: typing.Any) -> bool: + ... + def __lt__(self, other: typing.Any) -> bool: + ... + def __ne__(self, other: typing.Any) -> bool: + ... + def __repr__(self) -> str: + ... + def __setstate__(self, state: int) -> None: + ... + def __str__(self) -> str: + ... + @property + def name(self) -> str: + ... + @property + def value(self) -> int: + ... +class ClRegVar: + """ + A register variable within an expression + """ + @staticmethod + def _pybind11_conduit_v1_(*args, **kwargs): # type: ignore + ... + def __eq__(self, arg0: typing.Any) -> bool: + ... + def __hash__(self) -> int: + ... + def __init__(self, i: int) -> None: + """ + Construct from an integer identifier + """ + def __repr__(self) -> str: + ... + def __str__(self) -> str: + ... + @property + def index(self) -> int: + """ + :return: integer identifier for the variable + """ class ClassicalEvalOp(ClassicalOp): """ Evaluatable classical operation. @@ -3378,6 +3619,8 @@ class OpType: StatePreparationBox : A box for preparing quantum states using multiplexed-Ry and multiplexed-Rz gates DiagonalBox : A box for synthesising a diagonal unitary matrix into a sequence of multiplexed-Rz gates + + ClExpr : A classical expression """ AAMS: typing.ClassVar[OpType] # value = BRIDGE: typing.ClassVar[OpType] # value = @@ -3401,6 +3644,7 @@ class OpType: CY: typing.ClassVar[OpType] # value = CZ: typing.ClassVar[OpType] # value = CircBox: typing.ClassVar[OpType] # value = + ClExpr: typing.ClassVar[OpType] # value = ClassicalExpBox: typing.ClassVar[OpType] # value = ClassicalTransform: typing.ClassVar[OpType] # value = CnRx: typing.ClassVar[OpType] # value = @@ -3480,7 +3724,7 @@ class OpType: Z: typing.ClassVar[OpType] # value = ZZMax: typing.ClassVar[OpType] # value = ZZPhase: typing.ClassVar[OpType] # value = - __members__: typing.ClassVar[dict[str, OpType]] # value = {'Phase': , 'Z': , 'X': , 'Y': , 'S': , 'Sdg': , 'T': , 'Tdg': , 'V': , 'Vdg': , 'SX': , 'SXdg': , 'H': , 'Rx': , 'Ry': , 'Rz': , 'U1': , 'U2': , 'U3': , 'GPI': , 'GPI2': , 'AAMS': , 'TK1': , 'TK2': , 'CX': , 'CY': , 'CZ': , 'CH': , 'CV': , 'CVdg': , 'CSX': , 'CSXdg': , 'CS': , 'CSdg': , 'CRz': , 'CRx': , 'CRy': , 'CU1': , 'CU3': , 'CCX': , 'ECR': , 'SWAP': , 'CSWAP': , 'noop': , 'Barrier': , 'Label': , 'Branch': , 'Goto': , 'Stop': , 'BRIDGE': , 'Measure': , 'Reset': , 'CircBox': , 'PhasePolyBox': , 'Unitary1qBox': , 'Unitary2qBox': , 'Unitary3qBox': , 'ExpBox': , 'PauliExpBox': , 'PauliExpPairBox': , 'PauliExpCommutingSetBox': , 'TermSequenceBox': , 'QControlBox': , 'ToffoliBox': , 'ConjugationBox': , 'DummyBox': , 'CustomGate': , 'Conditional': , 'ISWAP': , 'PhasedISWAP': , 'XXPhase': , 'YYPhase': , 'ZZPhase': , 'XXPhase3': , 'PhasedX': , 'NPhasedX': , 'CnRx': , 'CnRy': , 'CnRz': , 'CnX': , 'CnY': , 'CnZ': , 'ZZMax': , 'ESWAP': , 'FSim': , 'Sycamore': , 'ISWAPMax': , 'ClassicalTransform': , 'WASM': , 'SetBits': , 'CopyBits': , 'RangePredicate': , 'ExplicitPredicate': , 'ExplicitModifier': , 'MultiBit': , 'ClassicalExpBox': , 'MultiplexorBox': , 'MultiplexedRotationBox': , 'MultiplexedU2Box': , 'MultiplexedTensoredU2Box': , 'StatePreparationBox': , 'DiagonalBox': } + __members__: typing.ClassVar[dict[str, OpType]] # value = {'Phase': , 'Z': , 'X': , 'Y': , 'S': , 'Sdg': , 'T': , 'Tdg': , 'V': , 'Vdg': , 'SX': , 'SXdg': , 'H': , 'Rx': , 'Ry': , 'Rz': , 'U1': , 'U2': , 'U3': , 'GPI': , 'GPI2': , 'AAMS': , 'TK1': , 'TK2': , 'CX': , 'CY': , 'CZ': , 'CH': , 'CV': , 'CVdg': , 'CSX': , 'CSXdg': , 'CS': , 'CSdg': , 'CRz': , 'CRx': , 'CRy': , 'CU1': , 'CU3': , 'CCX': , 'ECR': , 'SWAP': , 'CSWAP': , 'noop': , 'Barrier': , 'Label': , 'Branch': , 'Goto': , 'Stop': , 'BRIDGE': , 'Measure': , 'Reset': , 'CircBox': , 'PhasePolyBox': , 'Unitary1qBox': , 'Unitary2qBox': , 'Unitary3qBox': , 'ExpBox': , 'PauliExpBox': , 'PauliExpPairBox': , 'PauliExpCommutingSetBox': , 'TermSequenceBox': , 'QControlBox': , 'ToffoliBox': , 'ConjugationBox': , 'DummyBox': , 'CustomGate': , 'Conditional': , 'ISWAP': , 'PhasedISWAP': , 'XXPhase': , 'YYPhase': , 'ZZPhase': , 'XXPhase3': , 'PhasedX': , 'NPhasedX': , 'CnRx': , 'CnRy': , 'CnRz': , 'CnX': , 'CnY': , 'CnZ': , 'ZZMax': , 'ESWAP': , 'FSim': , 'Sycamore': , 'ISWAPMax': , 'ClassicalTransform': , 'WASM': , 'SetBits': , 'CopyBits': , 'RangePredicate': , 'ExplicitPredicate': , 'ExplicitModifier': , 'MultiBit': , 'ClassicalExpBox': , 'MultiplexorBox': , 'MultiplexedRotationBox': , 'MultiplexedU2Box': , 'MultiplexedTensoredU2Box': , 'StatePreparationBox': , 'DiagonalBox': , 'ClExpr': } noop: typing.ClassVar[OpType] # value = @staticmethod def _pybind11_conduit_v1_(*args, **kwargs): # type: ignore @@ -4143,6 +4387,54 @@ class WASMOp(ClassicalOp): """ Wasm module id. """ +class WiredClExpr: + """ + A classical expression defined over a sequence of bits + """ + @staticmethod + def _pybind11_conduit_v1_(*args, **kwargs): # type: ignore + ... + @staticmethod + def from_dict(arg0: dict) -> WiredClExpr: + """ + Construct from JSON-serializable dict representation + """ + def __eq__(self, arg0: typing.Any) -> bool: + ... + def __hash__(self) -> int: + """ + Hashing is not implemented for this class, attempting to hash an object will raise a type error + """ + def __init__(self, expr: ClExpr, bit_posn: dict[int, int] = {}, reg_posn: dict[int, list[int]] = {}, output_posn: list[int] = []) -> None: + """ + Construct from an expression with bit and register positions + """ + def __str__(self) -> str: + ... + def to_dict(self) -> dict: + """ + :return: JSON-serializable dict representation + """ + @property + def bit_posn(self) -> dict[int, int]: + """ + :return: bit positions + """ + @property + def expr(self) -> ClExpr: + """ + :return: expression + """ + @property + def output_posn(self) -> list[int]: + """ + :return: output positions + """ + @property + def reg_posn(self) -> dict[int, list[int]]: + """ + :return: register positions + """ def fresh_symbol(preferred: str = 'a') -> sympy.Symbol: """ Given some preferred symbol, this finds an appropriate suffix that will guarantee it has not yet been used in the current python session. diff --git a/pytket/pytket/_tket/passes.pyi b/pytket/pytket/_tket/passes.pyi index 1b8564f642..efc45514eb 100644 --- a/pytket/pytket/_tket/passes.pyi +++ b/pytket/pytket/_tket/passes.pyi @@ -440,12 +440,16 @@ def GlobalisePhasedX(squash: bool = True) -> BasePass: It is not recommended to use this pass with symbolic expressions, as in certain cases a blow-up in symbolic expression sizes may occur. """ -def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3) -> BasePass: +def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3, max_lookahead: int = 500, max_tqe_candidates: int = 500, seed: int = 0, allow_zzphase: bool = False) -> BasePass: """ Construct a pass that converts a circuit into a graph of Pauli gadgets to account for commutation and phase folding, and resynthesises them using a greedy algorithm adapted from arxiv.org/abs/2103.08602. The method for synthesising the final Clifford operator is adapted from arxiv.org/abs/2305.10966. :param discount_rate: Rate used to discount the cost impact from gadgets that are further away. Default to 0.7. :param depth_weight: Degree of depth optimisation. Default to 0.3. + :param max_tqe_candidates: Maximum number of 2-qubit Clifford gate candidates to evaluate at each step. Default to 500. + :param max_lookahead: Maximum lookahead when evaluating each Clifford gate candidate. Default to 500. + :param seed: Unsigned integer seed used for sampling candidates and tie breaking. Default to 0. + :param allow_zzphase: If set to True, allows the algorithm to implement 2-qubit rotations using ZZPhase gates when deemed optimal. Defaults to False. :return: a pass to perform the simplification """ def GuidedPauliSimp(strat: pytket._tket.transform.PauliSynthStrat = pytket._tket.transform.PauliSynthStrat.Sets, cx_config: pytket._tket.circuit.CXConfigType = pytket._tket.circuit.CXConfigType.Snake) -> BasePass: diff --git a/pytket/pytket/_tket/transform.pyi b/pytket/pytket/_tket/transform.pyi index 084c9a9cc5..4801b4238c 100644 --- a/pytket/pytket/_tket/transform.pyi +++ b/pytket/pytket/_tket/transform.pyi @@ -165,12 +165,16 @@ class Transform: It is not recommended to use this transformation with symbolic expressions, as in certain cases a blow-up in symbolic expression sizes may occur. """ @staticmethod - def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3) -> Transform: + def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3, max_tqe_candidates: int = 500, max_lookahead: int = 500, seed: int = 0, allow_zzphase: bool = False) -> Transform: """ Convert a circuit into a graph of Pauli gadgets to account for commutation and phase folding, and resynthesises them using a greedy algorithm adapted from arxiv.org/abs/2103.08602. The method for synthesising the final Clifford operator is adapted from arxiv.org/abs/2305.10966. :param discount_rate: Rate used to discount the cost impact from gadgets that are further away. Default to 0.7. :param depth_weight: Degree of depth optimisation. Default to 0.3. + :param max_tqe_candidates: Maximum number of 2-qubit Clifford gate candidates to evaluate at each step. Default to 500. + :param max_lookahead: Maximum lookahead when evaluating each Clifford gate candidate. Default to 500. + :param seed: Unsigned integer seed used for sampling candidates and tie breaking. Default to 0. + :param allow_zzphase: If set to True, allows the algorithm to implement 2-qubit rotations using ZZPhase gates when deemed optimal. Defaults to False. :return: a pass to perform the simplification """ @staticmethod diff --git a/pytket/pytket/circuit/__init__.py b/pytket/pytket/circuit/__init__.py index 510401369d..6040d896a0 100644 --- a/pytket/pytket/circuit/__init__.py +++ b/pytket/pytket/circuit/__init__.py @@ -63,7 +63,7 @@ def overload_add_wasm( self: Circuit, funcname: str, - filehandler: wasm.WasmFileHandler, + filehandler: wasm.WasmModuleHandler, list_i: Sequence[int], list_o: Sequence[int], args: Union[Sequence[int], Sequence[Bit]], @@ -72,7 +72,7 @@ def overload_add_wasm( ) -> Circuit: """Add a classical function call from a wasm file to the circuit. \n\n:param funcname: name of the function that is called - \n:param filehandler: wasm file handler to identify the wasm file + \n:param filehandler: wasm file or module handler to identify the wasm module \n:param list_i: list of the number of bits in the input variables \n:param list_o: list of the number of bits in the output variables \n:param args: vector of circuit bits the wasm op should be added to @@ -113,7 +113,7 @@ def overload_add_wasm( def overload_add_wasm_to_reg( self: Circuit, funcname: str, - filehandler: wasm.WasmFileHandler, + filehandler: wasm.WasmModuleHandler, list_i: Sequence[BitRegister], list_o: Sequence[BitRegister], args_wasm: Optional[Sequence[int]] = None, @@ -121,7 +121,7 @@ def overload_add_wasm_to_reg( ) -> Circuit: """Add a classical function call from a wasm file to the circuit. \n\n:param funcname: name of the function that is called - \n:param filehandler: wasm file handler to identify the wasm file + \n:param filehandler: wasm file or module handler to identify the wasm module \n:param list_i: list of the classical registers assigned to the input variables of the function call \n:param list_o: list of the classical registers assigned to diff --git a/pytket/pytket/circuit/clexpr.py b/pytket/pytket/circuit/clexpr.py new file mode 100644 index 0000000000..b6059c560c --- /dev/null +++ b/pytket/pytket/circuit/clexpr.py @@ -0,0 +1,163 @@ +# Copyright 2019-2024 Cambridge Quantum Computing +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from dataclasses import dataclass +from pytket.circuit import ( + Bit, + BitRegister, + ClBitVar, + ClExpr, + ClOp, + ClRegVar, + WiredClExpr, +) +from pytket.circuit.logic_exp import Ops, BitWiseOp, RegWiseOp, LogicExp + +_reg_output_clops = set( + [ + ClOp.RegAnd, + ClOp.RegOr, + ClOp.RegXor, + ClOp.RegNot, + ClOp.RegZero, + ClOp.RegOne, + ClOp.RegAdd, + ClOp.RegSub, + ClOp.RegMul, + ClOp.RegDiv, + ClOp.RegPow, + ClOp.RegLsh, + ClOp.RegRsh, + ClOp.RegNeg, + ] +) + + +def has_reg_output(op: ClOp) -> bool: + return op in _reg_output_clops + + +def clop_from_ops(op: Ops) -> ClOp: + match op: + case BitWiseOp.AND: + return ClOp.BitAnd + case BitWiseOp.OR: + return ClOp.BitOr + case BitWiseOp.XOR: + return ClOp.BitXor + case BitWiseOp.EQ: + return ClOp.BitEq + case BitWiseOp.NEQ: + return ClOp.BitNeq + case BitWiseOp.NOT: + return ClOp.BitNot + case BitWiseOp.ZERO: + return ClOp.BitZero + case BitWiseOp.ONE: + return ClOp.BitOne + case RegWiseOp.AND: + return ClOp.RegAnd + case RegWiseOp.OR: + return ClOp.RegOr + case RegWiseOp.XOR: + return ClOp.RegXor + case RegWiseOp.EQ: + return ClOp.RegEq + case RegWiseOp.NEQ: + return ClOp.RegNeq + case RegWiseOp.LT: + return ClOp.RegLt + case RegWiseOp.GT: + return ClOp.RegGt + case RegWiseOp.LEQ: + return ClOp.RegLeq + case RegWiseOp.GEQ: + return ClOp.RegGeq + case RegWiseOp.ADD: + return ClOp.RegAdd + case RegWiseOp.SUB: + return ClOp.RegSub + case RegWiseOp.MUL: + return ClOp.RegMul + case RegWiseOp.DIV: + return ClOp.RegDiv + case RegWiseOp.POW: + return ClOp.RegPow + case RegWiseOp.LSH: + return ClOp.RegLsh + case RegWiseOp.RSH: + return ClOp.RegRsh + case RegWiseOp.NOT: + return ClOp.RegNot + case RegWiseOp.NEG: + return ClOp.RegNeg + + +@dataclass +class ExpressionConverter: + bit_indices: dict[Bit, int] + reg_indices: dict[BitRegister, int] + + def convert(self, exp: LogicExp) -> ClExpr: + op: ClOp = clop_from_ops(exp.op) + args: list[int | ClBitVar | ClRegVar | ClExpr] = [] + for arg in exp.args: + if isinstance(arg, LogicExp): + args.append(self.convert(arg)) + elif isinstance(arg, Bit): + args.append(ClBitVar(self.bit_indices[arg])) + elif isinstance(arg, BitRegister): + args.append(ClRegVar(self.reg_indices[arg])) + else: + assert isinstance(arg, int) + args.append(arg) + return ClExpr(op, args) + + +def wired_clexpr_from_logic_exp( + exp: LogicExp, output_bits: list[Bit] +) -> tuple[WiredClExpr, list[Bit]]: + """Convert a :py:class:`LogicExp` to a :py:class:`WiredClExpr` + + :param exp: the LogicExp + :param output_bits: list of output bits of the LogicExp + :return: the WiredClExpr and its full list of arguments + """ + # 1. Construct lists of input bits and registers (where the positions of the items + # in each list will be the indices of the corresponding variables in the ClExpr): + all_vars = exp.all_inputs_ordered() + input_bits: list[Bit] = [var for var in all_vars if isinstance(var, Bit)] + input_regs: list[BitRegister] = [ + var for var in all_vars if isinstance(var, BitRegister) + ] + # 2. Order the arguments: first the input bits, then all the bits in the input + # registers then any remaining output bits: + args = [] + args.extend(input_bits) + for r in input_regs: + args.extend(r.to_list()) + args.extend(b for b in output_bits if b not in args) + # 3. Construct the WiredClExpr and return it with the argument list: + return ( + WiredClExpr( + ExpressionConverter( + {b: i for i, b in enumerate(input_bits)}, + {r: i for i, r in enumerate(input_regs)}, + ).convert(exp), + {i: args.index(b) for i, b in enumerate(input_bits)}, + {i: [args.index(b) for b in r.to_list()] for i, r in enumerate(input_regs)}, + [args.index(b) for b in output_bits], + ), + args, + ) diff --git a/pytket/pytket/circuit/display/__init__.py b/pytket/pytket/circuit/display/__init__.py index 0841b5324a..38483197c0 100644 --- a/pytket/pytket/circuit/display/__init__.py +++ b/pytket/pytket/circuit/display/__init__.py @@ -127,9 +127,11 @@ class CircuitDisplayConfig(PytketExtConfig): @classmethod def from_extension_dict(cls, ext_dict: dict[str, Any]) -> "CircuitDisplayConfig": + min_h = ext_dict.get("min_height") + min_w = ext_dict.get("min_width") return CircuitDisplayConfig( - min_height=str(ext_dict.get("min_height")), - min_width=str(ext_dict.get("min_width")), + min_height=str(min_h) if min_h is not None else "400px", + min_width=str(min_w) if min_w is not None else "500px", orient=ext_dict.get("orient"), render_options=RenderOptions( **(ext_dict["render_options"] if "render_options" in ext_dict else {}) diff --git a/pytket/pytket/circuit/logic_exp.py b/pytket/pytket/circuit/logic_exp.py index d350d71980..9ba71c2317 100644 --- a/pytket/pytket/circuit/logic_exp.py +++ b/pytket/pytket/circuit/logic_exp.py @@ -245,9 +245,7 @@ def from_dict(cls, dic: Dict[str, Any]) -> "LogicExp": """Load from JSON serializable nested dictionary.""" opset_name, op_name = dic["op"].split(".", 2) opset = BitWiseOp if opset_name == "BitWiseOp" else RegWiseOp - op = cast( - Union[BitWiseOp, RegWiseOp], next(o for o in opset if o.name == op_name) - ) + op = next(o for o in opset if o.name == op_name) args: List[ArgType] = [] for arg_ser in dic["args"]: if isinstance(arg_ser, Constant): diff --git a/pytket/pytket/qasm/qasm.py b/pytket/pytket/qasm/qasm.py index 9a01d6e167..86cba07ed1 100644 --- a/pytket/pytket/qasm/qasm.py +++ b/pytket/pytket/qasm/qasm.py @@ -54,6 +54,9 @@ MultiBitOp, WASMOp, BarrierOp, + ClExprOp, + WiredClExpr, + ClExpr, ) from pytket._tket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE from pytket.circuit import ( @@ -66,6 +69,7 @@ QubitRegister, UnitID, ) +from pytket.circuit.clexpr import has_reg_output, wired_clexpr_from_logic_exp from pytket.circuit.decompose_classical import int_to_bools from pytket.circuit.logic_exp import ( BitLogicExp, @@ -298,7 +302,12 @@ def __iter__(self) -> Iterable[str]: class CircuitTransformer(Transformer): - def __init__(self, return_gate_dict: bool = False, maxwidth: int = 32) -> None: + def __init__( + self, + return_gate_dict: bool = False, + maxwidth: int = 32, + use_clexpr: bool = False, + ) -> None: super().__init__() self.q_registers: Dict[str, int] = {} self.c_registers: Dict[str, int] = {} @@ -307,6 +316,7 @@ def __init__(self, return_gate_dict: bool = False, maxwidth: int = 32) -> None: self.include = "" self.return_gate_dict = return_gate_dict self.maxwidth = maxwidth + self.use_clexpr = use_clexpr def _fresh_temp_bit(self) -> List: if _TEMP_BIT_NAME in self.c_registers: @@ -715,6 +725,29 @@ def _cexpbox_dict(self, exp: LogicExp, args: List[List]) -> CommandDict: }, } + def _clexpr_dict(self, exp: LogicExp, out_args: List[List]) -> CommandDict: + # Convert the LogicExp to a serialization of a command containing the + # corresponding ClExprOp. + wexpr, args = wired_clexpr_from_logic_exp( + exp, [Bit.from_list(arg) for arg in out_args] + ) + return { + "op": { + "type": "ClExpr", + "expr": wexpr.to_dict(), + }, + "args": [arg.to_list() for arg in args], + } + + def _logic_exp_as_cmd_dict( + self, exp: LogicExp, out_args: List[List] + ) -> CommandDict: + return ( + self._clexpr_dict(exp, out_args) + if self.use_clexpr + else self._cexpbox_dict(exp, out_args) + ) + def assign(self, tree: List) -> Iterable[CommandDict]: child_iter = iter(tree) out_args = list(next(child_iter)) @@ -752,7 +785,7 @@ def assign(self, tree: List) -> Iterable[CommandDict]: args = args_uids[0] if isinstance(out_arg, List): if isinstance(exp, LogicExp): - yield self._cexpbox_dict(exp, args) + yield self._logic_exp_as_cmd_dict(exp, args) elif isinstance(exp, (int, bool)): assert exp in (0, 1, True, False) yield { @@ -769,9 +802,9 @@ def assign(self, tree: List) -> Iterable[CommandDict]: else: reg = out_arg if isinstance(exp, RegLogicExp): - yield self._cexpbox_dict(exp, args) + yield self._logic_exp_as_cmd_dict(exp, args) elif isinstance(exp, BitLogicExp): - yield self._cexpbox_dict(exp, args[:1]) + yield self._logic_exp_as_cmd_dict(exp, args[:1]) elif isinstance(exp, int): yield { "args": args, @@ -926,38 +959,42 @@ def prog(self, tree: Iterable) -> Dict[str, Any]: return outdict -def parser(maxwidth: int) -> Lark: +def parser(maxwidth: int, use_clexpr: bool) -> Lark: return Lark( grammar, start="prog", debug=False, parser="lalr", cache=True, - transformer=CircuitTransformer(maxwidth=maxwidth), + transformer=CircuitTransformer(maxwidth=maxwidth, use_clexpr=use_clexpr), ) g_parser = None g_maxwidth = 32 +g_use_clexpr = False -def set_parser(maxwidth: int) -> None: - global g_parser, g_maxwidth - if (g_parser is None) or (g_maxwidth != maxwidth): # type: ignore - g_parser = parser(maxwidth=maxwidth) +def set_parser(maxwidth: int, use_clexpr: bool) -> None: + global g_parser, g_maxwidth, g_use_clexpr + if (g_parser is None) or (g_maxwidth != maxwidth) or g_use_clexpr != use_clexpr: # type: ignore + g_parser = parser(maxwidth=maxwidth, use_clexpr=use_clexpr) g_maxwidth = maxwidth + g_use_clexpr = use_clexpr def circuit_from_qasm( input_file: Union[str, "os.PathLike[Any]"], encoding: str = "utf-8", maxwidth: int = 32, + use_clexpr: bool = False, ) -> Circuit: """A method to generate a tket Circuit from a qasm file. :param input_file: path to qasm file; filename must have ``.qasm`` extension :param encoding: file encoding (default utf-8) :param maxwidth: maximum allowed width of classical registers (default 32) + :param use_clexpr: whether to use ClExprOp to represent classical expressions :return: pytket circuit """ ext = os.path.splitext(input_file)[-1] @@ -965,21 +1002,24 @@ def circuit_from_qasm( raise TypeError("Can only convert .qasm files") with open(input_file, "r", encoding=encoding) as f: try: - circ = circuit_from_qasm_io(f, maxwidth=maxwidth) + circ = circuit_from_qasm_io(f, maxwidth=maxwidth, use_clexpr=use_clexpr) except QASMParseError as e: raise QASMParseError(e.msg, e.line, str(input_file)) return circ -def circuit_from_qasm_str(qasm_str: str, maxwidth: int = 32) -> Circuit: +def circuit_from_qasm_str( + qasm_str: str, maxwidth: int = 32, use_clexpr: bool = False +) -> Circuit: """A method to generate a tket Circuit from a qasm string. :param qasm_str: qasm string :param maxwidth: maximum allowed width of classical registers (default 32) + :param use_clexpr: whether to use ClExprOp to represent classical expressions :return: pytket circuit """ global g_parser - set_parser(maxwidth=maxwidth) + set_parser(maxwidth=maxwidth, use_clexpr=use_clexpr) assert g_parser is not None cast(CircuitTransformer, g_parser.options.transformer)._reset_context( reset_wasm=False @@ -987,9 +1027,13 @@ def circuit_from_qasm_str(qasm_str: str, maxwidth: int = 32) -> Circuit: return Circuit.from_dict(g_parser.parse(qasm_str)) # type: ignore[arg-type] -def circuit_from_qasm_io(stream_in: TextIO, maxwidth: int = 32) -> Circuit: +def circuit_from_qasm_io( + stream_in: TextIO, maxwidth: int = 32, use_clexpr: bool = False +) -> Circuit: """A method to generate a tket Circuit from a qasm text stream""" - return circuit_from_qasm_str(stream_in.read(), maxwidth=maxwidth) + return circuit_from_qasm_str( + stream_in.read(), maxwidth=maxwidth, use_clexpr=use_clexpr + ) def circuit_from_qasm_wasm( @@ -997,6 +1041,7 @@ def circuit_from_qasm_wasm( wasm_file: Union[str, "os.PathLike[Any]"], encoding: str = "utf-8", maxwidth: int = 32, + use_clexpr: bool = False, ) -> Circuit: """A method to generate a tket Circuit from a qasm string and external WASM module. @@ -1008,10 +1053,12 @@ def circuit_from_qasm_wasm( """ global g_parser wasm_module = WasmFileHandler(str(wasm_file)) - set_parser(maxwidth=maxwidth) + set_parser(maxwidth=maxwidth, use_clexpr=use_clexpr) assert g_parser is not None cast(CircuitTransformer, g_parser.options.transformer).wasm = wasm_module - return circuit_from_qasm(input_file, encoding=encoding, maxwidth=maxwidth) + return circuit_from_qasm( + input_file, encoding=encoding, maxwidth=maxwidth, use_clexpr=use_clexpr + ) def circuit_to_qasm( @@ -1718,6 +1765,51 @@ def add_classical_exp_box(self, op: ClassicalExpBox, args: List[Bit]) -> None: " for writing to a single bit or whole registers." ) + def add_wired_clexpr(self, op: ClExprOp, args: List[Bit]) -> None: + wexpr: WiredClExpr = op.expr + # 1. Determine the mappings from bit variables to bits and from register + # variables to registers. + expr: ClExpr = wexpr.expr + bit_posn: dict[int, int] = wexpr.bit_posn + reg_posn: dict[int, list[int]] = wexpr.reg_posn + output_posn: list[int] = wexpr.output_posn + input_bits: dict[int, Bit] = {i: args[j] for i, j in bit_posn.items()} + input_regs: dict[int, BitRegister] = {} + all_cregs = set(self.cregs.values()) + for i, posns in reg_posn.items(): + reg_args = [args[j] for j in posns] + for creg in all_cregs: + if creg.to_list() == reg_args: + input_regs[i] = creg + break + else: + raise QASMUnsupportedError( + f"ClExprOp ({wexpr}) contains a register variable (r{i}) " + "that is not wired to any BitRegister in the circuit." + ) + # 2. Write the left-hand side of the assignment. + output_repr: Optional[str] = None + output_args: list[Bit] = [args[j] for j in output_posn] + n_output_args = len(output_args) + expect_reg_output = has_reg_output(expr.op) + if n_output_args == 0: + raise QASMUnsupportedError("Expression has no output.") + elif n_output_args == 1: + output_arg = output_args[0] + output_repr = output_arg.reg_name if expect_reg_output else str(output_arg) + else: + if not expect_reg_output: + raise QASMUnsupportedError("Unexpected output for operation.") + for creg in all_cregs: + if creg.to_list() == output_args: + output_repr = creg.name + self.strings.add_string(f"{output_repr} = ") + # 3. Write the right-hand side of the assignment. + self.strings.add_string( + expr.as_qasm(input_bits=input_bits, input_regs=input_regs) + ) + self.strings.add_string(";\n") + def add_wasm(self, op: WASMOp, args: List[Bit]) -> None: inputs: List[str] = [] outputs: List[str] = [] @@ -1821,6 +1913,9 @@ def add_op(self, op: Op, args: Sequence[UnitID]) -> None: elif optype == OpType.ClassicalExpBox: assert isinstance(op, ClassicalExpBox) self.add_classical_exp_box(op, cast(List[Bit], args)) + elif optype == OpType.ClExpr: + assert isinstance(op, ClExprOp) + self.add_wired_clexpr(op, cast(List[Bit], args)) elif optype == OpType.WASM: assert isinstance(op, WASMOp) self.add_wasm(op, cast(List[Bit], args)) diff --git a/pytket/pytket/quipper/quipper.py b/pytket/pytket/quipper/quipper.py index 5c0d4d8328..5ec0e20736 100644 --- a/pytket/pytket/quipper/quipper.py +++ b/pytket/pytket/quipper/quipper.py @@ -300,7 +300,7 @@ def subroutine_call(self, t: List) -> SubroutineCall: ) def comment(self, t: List) -> Comment: - wire_comments = t[2] if len(t) > 2 else None + wire_comments = t[2] if len(t) > 2 else [] return Comment( comment=t[0], inverted=len(t[1].children) > 0, wire_comments=wire_comments ) diff --git a/pytket/pytket/utils/distribution.py b/pytket/pytket/utils/distribution.py index 2deba9d9cc..9985f6c80d 100644 --- a/pytket/pytket/utils/distribution.py +++ b/pytket/pytket/utils/distribution.py @@ -64,7 +64,7 @@ def as_counter(self) -> Counter[T0]: @property def total(self) -> int: """Return the total number of observations.""" - return sum(self._C.values()) # Counter.total() new in 3.10 + return self._C.total() @property def support(self) -> Set[T0]: diff --git a/pytket/pytket/wasm/__init__.py b/pytket/pytket/wasm/__init__.py index f0efd99e27..65d3648f7f 100644 --- a/pytket/pytket/wasm/__init__.py +++ b/pytket/pytket/wasm/__init__.py @@ -17,4 +17,5 @@ from .wasm import ( WasmFileHandler, + WasmModuleHandler, ) diff --git a/pytket/setup.py b/pytket/setup.py index bf365ece59..14f2000f02 100755 --- a/pytket/setup.py +++ b/pytket/setup.py @@ -219,6 +219,7 @@ def finalize_options(self): "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", diff --git a/pytket/tests/classical_test.py b/pytket/tests/classical_test.py index ceeda6b421..e365074270 100644 --- a/pytket/tests/classical_test.py +++ b/pytket/tests/classical_test.py @@ -192,6 +192,18 @@ def test_wasm_3() -> None: assert c.depth() == 1 +def test_wasm_3_bytes() -> None: + with open("testfile.wasm", "rb") as f: + bytecode = f.read() + c = Circuit(0, 6) + + w = wasm.WasmModuleHandler(bytecode) + + c.add_wasm("add_one", w, [1], [1], [Bit(0), Bit(1)]) + + assert c.depth() == 1 + + def test_wasm_4() -> None: w = wasm.WasmFileHandler("testfile.wasm") @@ -412,6 +424,19 @@ def test_wasm_function_check_6() -> None: assert c.depth() == 1 +def test_wasm_function_check_6_bytes() -> None: + with open("testfile.wasm", "rb") as f: + bytecode = f.read() + + w = wasm.WasmModuleHandler(bytecode) + c = Circuit(20, 20) + c0 = c.add_c_register("c0", 32) + c1 = c.add_c_register("c1", 4) + + c.add_wasm_to_reg("add_one", w, [c0], [c1]) + assert c.depth() == 1 + + def test_wasm_function_check_7() -> None: w = wasm.WasmFileHandler("testfile.wasm", int_size=32) c = Circuit(20, 20) diff --git a/pytket/tests/clexpr_test.py b/pytket/tests/clexpr_test.py new file mode 100644 index 0000000000..e078224aa0 --- /dev/null +++ b/pytket/tests/clexpr_test.py @@ -0,0 +1,219 @@ +# Copyright 2019-2024 Cambridge Quantum Computing +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pytket.circuit import ( + Bit, + CircBox, + Circuit, + ClBitVar, + ClExpr, + ClExprOp, + ClOp, + ClRegVar, + OpType, + WiredClExpr, +) +from pytket.qasm import circuit_to_qasm_str, circuit_from_qasm_str + + +def test_op() -> None: + reg_add = ClOp.RegAdd + assert str(reg_add) == "ClOp.RegAdd" + reg_sub = ClOp.RegSub + assert reg_add != reg_sub + + +def test_vars() -> None: + bvar3 = ClBitVar(3) + assert bvar3.index == 3 + assert str(bvar3) == "b3" + bvar4 = ClBitVar(4) + rvar3 = ClRegVar(3) + assert rvar3.index == 3 + assert str(rvar3) == "r3" + rvar3a = ClRegVar(3) + assert bvar3 != bvar4 + assert bvar3 != rvar3 + assert rvar3 == rvar3a + + +def test_expr() -> None: + b0 = ClBitVar(0) + r0 = ClRegVar(0) + three = 3 + expr0 = ClExpr(op=ClOp.RegAdd, args=[r0, three]) + expr = ClExpr(op=ClOp.BitXor, args=[expr0, b0]) + assert str(expr) == "xor(add(r0, 3), b0)" + assert expr.op == ClOp.BitXor + args = expr.args + assert len(args) == 2 + assert args[0] == expr0 + assert args[1] == b0 + + +def test_wexpr() -> None: + expr = ClExpr( + op=ClOp.RegDiv, + args=[ClRegVar(0), ClExpr(op=ClOp.RegAdd, args=[2, ClBitVar(0)])], + ) + wexpr = WiredClExpr( + expr=expr, bit_posn={0: 1}, reg_posn={0: [2, 0]}, output_posn=[2, 0] + ) + assert str(wexpr) == "div(r0, add(2, b0)) [b0:1, r0:(2,0) --> (2,0)]" + assert wexpr.expr == expr + assert wexpr.bit_posn == {0: 1} + assert wexpr.reg_posn == {0: [2, 0]} + assert wexpr.output_posn == [2, 0] + wexpr_dict = wexpr.to_dict() + assert wexpr_dict == { + "bit_posn": [[0, 1]], + "expr": { + "args": [ + { + "input": { + "term": {"type": "reg", "var": {"index": 0}}, + "type": "var", + }, + "type": "term", + }, + { + "input": { + "args": [ + {"input": {"term": 2, "type": "int"}, "type": "term"}, + { + "input": { + "term": {"type": "bit", "var": {"index": 0}}, + "type": "var", + }, + "type": "term", + }, + ], + "op": "RegAdd", + }, + "type": "expr", + }, + ], + "op": "RegDiv", + }, + "output_posn": [2, 0], + "reg_posn": [[0, [2, 0]]], + } + wexpr1 = WiredClExpr.from_dict(wexpr_dict) + assert wexpr == wexpr1 + + +def test_adding_to_circuit() -> None: + expr = ClExpr(op=ClOp.BitXor, args=[ClBitVar(0), ClBitVar(1)]) + wexpr = WiredClExpr(expr=expr, bit_posn={0: 0, 1: 1}, output_posn=[2]) + c = Circuit(0, 3) + c.add_clexpr(wexpr, c.bits) + cmds = c.get_commands() + assert len(cmds) == 1 + op = cmds[0].op + assert isinstance(op, ClExprOp) + assert op.expr == wexpr + d = c.to_dict() + c1 = Circuit.from_dict(d) + assert c == c1 + d1 = c1.to_dict() + assert d == d1 + c2 = c.copy() + assert c2 == c + + +def test_qasm_conversion() -> None: + c = Circuit() + c.add_c_register("a", 3) + c.add_c_register("b", 3) + c.add_c_register("c", 3) + c.add_c_register("d", 3) + expr = ClExpr( + op=ClOp.RegSub, + args=[ + ClExpr( + op=ClOp.RegDiv, + args=[ClExpr(ClOp.RegAdd, args=[ClRegVar(0), ClRegVar(1)]), 2], + ), + ClRegVar(2), + ], + ) + wexpr = WiredClExpr( + expr=expr, + reg_posn={0: [0, 1, 2], 1: [3, 4, 5], 2: [6, 7, 8]}, + output_posn=[9, 10, 11], + ) + c.add_clexpr(wexpr, c.bits) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +creg a[3]; +creg b[3]; +creg c[3]; +creg d[3]; +d = (((a + b) / 2) - c); +""" + ) + c1 = circuit_from_qasm_str(qasm, use_clexpr=True) + assert c == c1 + + +def make_circ() -> Circuit: + c = Circuit() + c.add_bit(Bit("x", 0)) + c.add_bit(Bit("x", 1)) + c.add_bit(Bit("y", 0)) + c.add_clexpr( + WiredClExpr( + expr=ClExpr(op=ClOp.BitXor, args=[ClBitVar(0), ClBitVar(1)]), + bit_posn={0: 0, 1: 1}, + output_posn=[2], + ), + [Bit("x", 0), Bit("x", 1), Bit("y", 0)], + ) + return c + + +def test_copy_and_flatten() -> None: + # See https://github.com/CQCL/tket/issues/1544 + c0 = make_circ() + c1 = make_circ() + assert c0 == c1 + c2 = c1.copy() + c2.flatten_registers() + assert c0 == c1 + assert c2.get_commands()[0].op == c0.get_commands()[0].op + qasm = circuit_to_qasm_str(c2, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +creg c[3]; +c[2] = (c[0] ^ c[1]); +""" + ) + + +def test_circbox() -> None: + # See https://github.com/CQCL/tket/issues/1544 + c0 = make_circ() + cbox = CircBox(c0) + c1 = Circuit(0, 3) + c1.add_circbox(cbox, [0, 1, 2]) + c2 = c1.copy() + c2.flatten_registers() + assert c1 == c2 diff --git a/pytket/tests/passes_serialisation_test.py b/pytket/tests/passes_serialisation_test.py index f7d7d788b6..193ece5786 100644 --- a/pytket/tests/passes_serialisation_test.py +++ b/pytket/tests/passes_serialisation_test.py @@ -290,7 +290,15 @@ def nonparam_predicate_dict(name: str) -> Dict[str, Any]: {"name": "RoundAngles", "n": 6, "only_zeros": False} ), "GreedyPauliSimp": standard_pass_dict( - {"name": "GreedyPauliSimp", "discount_rate": 0.4, "depth_weight": 0.5} + { + "name": "GreedyPauliSimp", + "discount_rate": 0.4, + "depth_weight": 0.5, + "max_lookahead": 100, + "max_tqe_candidates": 100, + "seed": 2, + "allow_zzphase": True, + } ), # lists must be sorted by OpType value "AutoSquash": standard_pass_dict( diff --git a/pytket/tests/predicates_test.py b/pytket/tests/predicates_test.py index 186c190cb5..ea805321a4 100644 --- a/pytket/tests/predicates_test.py +++ b/pytket/tests/predicates_test.py @@ -27,6 +27,9 @@ UnitID, Conditional, Bit, + RangePredicateOp, + SetBitsOp, + MultiBitOp, ) from pytket.circuit.named_types import ParamType, RenameUnitsMap from pytket.pauli import Pauli @@ -1020,18 +1023,61 @@ def test_clifford_push_through_measures() -> None: assert coms[7].op.type == OpType.CopyBits -def greedy_pauli_synth() -> None: - circ = Circuit(4, name="test") +def test_greedy_pauli_synth() -> None: + circ = Circuit(name="test") rega = circ.add_q_register("a", 2) regb = circ.add_q_register("b", 2) - d = circ.copy() circ.Rz(0, rega[0]).H(regb[1]).CX(rega[0], rega[1]).Ry(0.3, rega[0]).S(regb[1]).CZ( rega[0], regb[0] ).SWAP(regb[1], rega[0]) + d = circ.copy() pss = GreedyPauliSimp(0.5, 0.5) assert pss.apply(d) assert np.allclose(circ.get_unitary(), d.get_unitary()) assert d.name == "test" + # test gateset + range_predicate = RangePredicateOp(3, 0, 6) + set_bits = SetBitsOp([True, True]) + multi_bit = MultiBitOp(set_bits, 2) + exp = Bit(2) & Bit(3) + eq_pred_values = [True, False, False, True] + and_values = [bool(i) for i in [0, 0, 0, 1]] + pg1 = PauliExpBox([Pauli.X, Pauli.Z], 0.3) + circ = Circuit(4, 4, name="test") + circ.add_pauliexpbox(pg1, [0, 1]) + circ.add_gate(multi_bit, [0, 1, 2, 3]) + circ.add_gate(range_predicate, [0, 1, 2, 3]) + circ.add_classicalexpbox_bit(exp, [Bit(0)]) + circ.add_c_predicate(eq_pred_values, [0, 1], 2, "EQ") + circ.add_c_modifier(and_values, [1], 2) + circ._add_wasm("funcname", "wasmfileuid", [1, 1], [], [Bit(0), Bit(1)], [0]) + circ.measure_all() + circ.Reset(0) + circ.add_pauliexpbox(pg1, [2, 3]) + assert GreedyPauliSimp(0.5, 0.5, 100, 100, 0, True).apply(circ) + # PauliExpBoxes implemented using ZZPhase + d = Circuit(4, 4, name="test") + d.H(0) + d.ZZPhase(0.3, 0, 1) + d.H(0) + d.add_gate(multi_bit, [0, 1, 2, 3]) + d.add_gate(range_predicate, [0, 1, 2, 3]) + d.add_classicalexpbox_bit(exp, [Bit(0)]) + d.add_c_predicate(eq_pred_values, [0, 1], 2, "EQ") + d.add_c_modifier(and_values, [1], 2) + d._add_wasm("funcname", "wasmfileuid", [1, 1], [], [Bit(0), Bit(1)], [0]) + d.measure_all() + d.Reset(0) + d.H(2) + d.ZZPhase(0.3, 2, 3) + d.H(2) + assert circ == d + # test barrier + circ = Circuit(1).add_barrier([0]) + with pytest.raises(RuntimeError) as e: + GreedyPauliSimp().apply(circ) + err_msg = "Predicate requirements are not satisfied" + assert err_msg in str(e.value) def test_auto_rebase_deprecation(recwarn: Any) -> None: diff --git a/pytket/tests/qasm_test.py b/pytket/tests/qasm_test.py index 85b0a9a5db..8ed36b7fb4 100644 --- a/pytket/tests/qasm_test.py +++ b/pytket/tests/qasm_test.py @@ -422,7 +422,7 @@ def test_h1_rzz() -> None: def test_extended_qasm() -> None: fname = str(curr_file_path / "qasm_test_files/test17.qasm") out_fname = str(curr_file_path / "qasm_test_files/test17_output.qasm") - c = circuit_from_qasm_wasm(fname, "testfile.wasm") + c = circuit_from_qasm_wasm(fname, "testfile.wasm", use_clexpr=True) out_qasm = circuit_to_qasm_str(c, "hqslib1") with open(out_fname) as f: @@ -432,15 +432,14 @@ def test_extended_qasm() -> None: assert circuit_to_qasm_str(c2, "hqslib1") - with pytest.raises(DecomposeClassicalError) as e: - DecomposeClassicalExp().apply(c) + assert not DecomposeClassicalExp().apply(c) def test_decomposable_extended() -> None: fname = str(curr_file_path / "qasm_test_files/test18.qasm") out_fname = str(curr_file_path / "qasm_test_files/test18_output.qasm") - c = circuit_from_qasm_wasm(fname, "testfile.wasm", maxwidth=64) + c = circuit_from_qasm_wasm(fname, "testfile.wasm", maxwidth=64, use_clexpr=True) DecomposeClassicalExp().apply(c) out_qasm = circuit_to_qasm_str(c, "hqslib1", maxwidth=64) @@ -654,10 +653,11 @@ def test_qasm_phase() -> None: assert c1 == c0 -def test_CopyBits() -> None: +@pytest.mark.parametrize("use_clexpr", [True, False]) +def test_CopyBits(use_clexpr: bool) -> None: input_qasm = """OPENQASM 2.0;\ninclude "hqslib1.inc";\n\ncreg c0[1]; creg c1[3];\nc0[0] = c1[1];\n""" - c = circuit_from_qasm_str(input_qasm) + c = circuit_from_qasm_str(input_qasm, use_clexpr=use_clexpr) result_circ_qasm = circuit_to_qasm_str(c, "hqslib1") assert input_qasm == result_circ_qasm @@ -831,7 +831,8 @@ def test_max_reg_width() -> None: assert len(circ_out.bits) == 33 -def test_classical_expbox_arg_order() -> None: +@pytest.mark.parametrize("use_clexpr", [True, False]) +def test_classical_expbox_arg_order(use_clexpr: bool) -> None: qasm = """ OPENQASM 2.0; include "hqslib1.inc"; @@ -846,7 +847,7 @@ def test_classical_expbox_arg_order() -> None: c = a ^ b | d; """ - circ = circuit_from_qasm_str(qasm) + circ = circuit_from_qasm_str(qasm, use_clexpr=use_clexpr) args = circ.get_commands()[0].args expected_symbol_order = ["a", "b", "d", "c"] expected_index_order = [0, 1, 2, 3] @@ -1158,5 +1159,6 @@ def test_multibitop() -> None: test_header_stops_gate_definition() test_tk2_definition() test_rxxyyzz_conversion() - test_classical_expbox_arg_order() + test_classical_expbox_arg_order(True) + test_classical_expbox_arg_order(False) test_register_name_check() diff --git a/pytket/tests/qasm_test_files/test17_output.qasm b/pytket/tests/qasm_test_files/test17_output.qasm index 7312e3de10..4e467334f0 100644 --- a/pytket/tests/qasm_test_files/test17_output.qasm +++ b/pytket/tests/qasm_test_files/test17_output.qasm @@ -7,19 +7,19 @@ creg b[3]; creg c[4]; creg d[1]; c = 2; -d[0] = (a << 1); c[0] = a[0]; c[1] = a[1]; if(b!=2) c[1] = ((b[1] & a[1]) | a[0]); c = (b & a); b = (a + b); -b[1] = (b[0] ^ (~ b[2])); +b[1] = (b[0] ^ (~b[2])); c = (a - (b ** c)); -d[0] = (c >> 2); +d = (a << 1); +d = (c >> 2); c[0] = 1; -d[0] = (a[0] ^ 1); -CCE1(c); b = ((a * c) / b); +CCE1(c); +d[0] = (a[0] ^ 1); a = CCE2(a, b); if(c>=2) h q[0]; if(d[0]==1) rx(1.0*pi) q[0]; diff --git a/pytket/tests/qasm_test_files/test18_output.qasm b/pytket/tests/qasm_test_files/test18_output.qasm index f2ab0b7d65..c635f280ab 100644 --- a/pytket/tests/qasm_test_files/test18_output.qasm +++ b/pytket/tests/qasm_test_files/test18_output.qasm @@ -6,22 +6,16 @@ creg a[2]; creg b[3]; creg c[4]; creg d[1]; -creg tk_SCRATCH_BIT[7]; -creg tk_SCRATCH_BITREG_0[64]; c = 2; -tk_SCRATCH_BITREG_0[0] = b[0] & a[0]; -tk_SCRATCH_BITREG_0[1] = b[1] & a[1]; c[0] = a[0]; c[1] = a[1]; -if(b!=2) tk_SCRATCH_BIT[6] = b[1] & a[1]; -c[0] = tk_SCRATCH_BITREG_0[0] | d[0]; -if(b!=2) c[1] = tk_SCRATCH_BIT[6] | a[0]; -tk_SCRATCH_BIT[6] = 1; -d[0] = a[0] ^ tk_SCRATCH_BIT[6]; -if(c>=2) h q[0]; +if(b!=2) c[1] = ((b[1] & a[1]) | a[0]); +c = ((b & a) | d); +d[0] = (a[0] ^ 1); a = CCE(a, b); -if(c<=2) h q[0]; +if(c>=2) h q[0]; CCE(c); +if(c<=2) h q[0]; if(c<=1) h q[0]; if(c>=3) h q[0]; if(c!=2) h q[0]; diff --git a/schemas/compiler_pass_v1.json b/schemas/compiler_pass_v1.json index e3a807dde3..b8175c8b92 100644 --- a/schemas/compiler_pass_v1.json +++ b/schemas/compiler_pass_v1.json @@ -357,6 +357,22 @@ "depth_weight": { "type": "number", "definition": "parameter controlling the degree of depth optimisation in \"GreedyPauliSimp\"" + }, + "max_lookahead": { + "type": "number", + "definition": "parameter controlling the lookahead when evaluating candidates in \"GreedyPauliSimp\"" + }, + "max_tqe_candidates": { + "type": "number", + "definition": "parameter controlling the number of candidates to evaluate in \"GreedyPauliSimp\"" + }, + "seed": { + "type": "number", + "definition": "parameter controlling the random sampling and tie breaking in \"GreedyPauliSimp\"" + }, + "allow_zzphase": { + "type": "boolean", + "definition": "parameter controlling the use of ZZPhase gates in \"GreedyPauliSimp\"" } }, "required": [ @@ -883,9 +899,13 @@ "then": { "required": [ "discount_rate", - "depth_weight" + "depth_weight", + "max_lookahead", + "max_tqe_candidates", + "seed", + "allow_zzphase" ], - "maxProperties": 3 + "maxProperties": 7 } }, { diff --git a/tket/CMakeLists.txt b/tket/CMakeLists.txt index 2b55de0d7c..41808ece11 100644 --- a/tket/CMakeLists.txt +++ b/tket/CMakeLists.txt @@ -218,6 +218,7 @@ target_sources(tket src/MeasurementSetup/MeasurementSetup.cpp src/Ops/BarrierOp.cpp src/Ops/ClassicalOps.cpp + src/Ops/ClExpr.cpp src/Ops/FlowOp.cpp src/Ops/MetaOp.cpp src/Ops/Op.cpp @@ -249,6 +250,8 @@ target_sources(tket src/Transformations/ContextualReduction.cpp src/Transformations/Decomposition.cpp src/Transformations/GreedyPauliOptimisation.cpp + src/Transformations/GreedyPauliConverters.cpp + src/Transformations/GreedyPauliOps.cpp src/Transformations/MeasurePass.cpp src/Transformations/OptimisationPass.cpp src/Transformations/PauliOptimisation.cpp @@ -369,6 +372,7 @@ target_sources(tket include/tket/MeasurementSetup/MeasurementSetup.hpp include/tket/Ops/BarrierOp.hpp include/tket/Ops/ClassicalOps.hpp + include/tket/Ops/ClExpr.hpp include/tket/Ops/FlowOp.hpp include/tket/Ops/MetaOp.hpp include/tket/Ops/Op.hpp diff --git a/tket/conanfile.py b/tket/conanfile.py index 5986b00417..6439e45b7a 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.3.31" + version = "1.3.35" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/include/tket/OpType/OpType.hpp b/tket/include/tket/OpType/OpType.hpp index e1b6c8fa5a..12a64a0d55 100644 --- a/tket/include/tket/OpType/OpType.hpp +++ b/tket/include/tket/OpType/OpType.hpp @@ -770,7 +770,12 @@ enum class OpType { /** * See \ref DummyBox */ - DummyBox + DummyBox, + + /** + * Function defined over bits and sequences of bits treated as integers + */ + ClExpr, }; JSON_DECL(OpType) diff --git a/tket/include/tket/OpType/OpTypeFunctions.hpp b/tket/include/tket/OpType/OpTypeFunctions.hpp index f23a544cdc..b07a82e4e2 100644 --- a/tket/include/tket/OpType/OpTypeFunctions.hpp +++ b/tket/include/tket/OpType/OpTypeFunctions.hpp @@ -111,7 +111,7 @@ bool is_clifford_type(OpType optype); /** Test for measurement and reset gates */ bool is_projective_type(OpType optype); -/** Test for purely classical gates derived from ClassicalOp */ +/** Test for purely classical gates */ bool is_classical_type(OpType optype); /** Test for controlled gates */ diff --git a/tket/include/tket/Ops/ClExpr.hpp b/tket/include/tket/Ops/ClExpr.hpp new file mode 100644 index 0000000000..1adf73d696 --- /dev/null +++ b/tket/include/tket/Ops/ClExpr.hpp @@ -0,0 +1,306 @@ +// Copyright 2019-2024 Cambridge Quantum Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +/** + * @file + * @brief Classical expressions involving bits and registers + */ + +#include +#include +#include +#include +#include +#include + +#include "tket/Ops/Op.hpp" + +namespace tket { + +// TODO Use X or list macros to reduce boilerplate. + +/** + * An function acting on bits or bit registers + */ +enum class ClOp { + INVALID, /// Invalid + BitAnd, /// Bitwise AND + BitOr, /// Bitwise OR + BitXor, /// Bitwise XOR + BitEq, /// Bitwise equality + BitNeq, /// Bitwise inequality + BitNot, /// Bitwise NOT + BitZero, /// Constant zero bit + BitOne, /// Constant one bit + RegAnd, /// Registerwise AND + RegOr, /// Registerwise OR + RegXor, /// Registerwise XOR + RegEq, /// Registerwise equality + RegNeq, /// Registerwise inequality + RegNot, /// Registerwise NOT + RegZero, /// Constant all-zeros register + RegOne, /// Constant all-ones register + RegLt, /// Integer less-than comparison + RegGt, /// Integer greater-than comparison + RegLeq, /// Integer less-than-or-equal comparison + RegGeq, /// Integer greater-than-or-equal comparison + RegAdd, /// Integer addition + RegSub, /// Integer subtraction + RegMul, /// Integer multiplication + RegDiv, /// Integer division + RegPow, /// Integer exponentiation + RegLsh, /// Left shift + RegRsh, /// Right shift + RegNeg /// Integer negation +}; + +std::ostream& operator<<(std::ostream& os, ClOp fn); + +NLOHMANN_JSON_SERIALIZE_ENUM( + ClOp, { + {ClOp::INVALID, "INVALID"}, {ClOp::BitAnd, "BitAnd"}, + {ClOp::BitOr, "BitOr"}, {ClOp::BitXor, "BitXor"}, + {ClOp::BitEq, "BitEq"}, {ClOp::BitNeq, "BitNeq"}, + {ClOp::BitNot, "BitNot"}, {ClOp::BitZero, "BitZero"}, + {ClOp::BitOne, "BitOne"}, {ClOp::RegAnd, "RegAnd"}, + {ClOp::RegOr, "RegOr"}, {ClOp::RegXor, "RegXor"}, + {ClOp::RegEq, "RegEq"}, {ClOp::RegNeq, "RegNeq"}, + {ClOp::RegNot, "RegNot"}, {ClOp::RegZero, "RegZero"}, + {ClOp::RegOne, "RegOne"}, {ClOp::RegLt, "RegLt"}, + {ClOp::RegGt, "RegGt"}, {ClOp::RegLeq, "RegLeq"}, + {ClOp::RegGeq, "RegGeq"}, {ClOp::RegAdd, "RegAdd"}, + {ClOp::RegSub, "RegSub"}, {ClOp::RegMul, "RegMul"}, + {ClOp::RegDiv, "RegDiv"}, {ClOp::RegPow, "RegPow"}, + {ClOp::RegLsh, "RegLsh"}, {ClOp::RegRsh, "RegRsh"}, + {ClOp::RegNeg, "RegNeg"}, + }) + +/** + * A bit variable within an expression + */ +typedef struct ClBitVar { + unsigned index; /// Identifier for the variable within the expression + bool operator==(const ClBitVar& other) const { return index == other.index; } + friend std::ostream& operator<<(std::ostream& os, const ClBitVar& var); +} ClBitVar; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ClBitVar, index) + +/** + * A register variable within an expression + */ +typedef struct ClRegVar { + unsigned index; /// Identifier for the variable within the expression + bool operator==(const ClRegVar& other) const { return index == other.index; } + friend std::ostream& operator<<(std::ostream& os, const ClRegVar& var); +} ClRegVar; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ClRegVar, index) + +/** + * A (bit or register) variable within an expression + */ +typedef std::variant ClExprVar; + +std::ostream& operator<<(std::ostream& os, const ClExprVar& var); + +void to_json(nlohmann::json& j, const ClExprVar& var); + +void from_json(const nlohmann::json& j, ClExprVar& var); + +/** + * A term in a classical expression (either a constant or a variable) + */ +typedef std::variant ClExprTerm; + +std::ostream& operator<<(std::ostream& os, const ClExprTerm& term); + +void to_json(nlohmann::json& j, const ClExprTerm& term); + +void from_json(const nlohmann::json& j, ClExprTerm& term); + +class ClExpr; + +/** + * An argument to a classical operation in an expression + */ +typedef std::variant ClExprArg; + +std::ostream& operator<<(std::ostream& os, const ClExprArg& arg); + +void to_json(nlohmann::json& j, const ClExprArg& arg); + +void from_json(const nlohmann::json& j, ClExprArg& arg); + +/** + * A classical expression + * + * It may be composed of subexpressions. + */ +class ClExpr { + public: + /** + * Default constructor + */ + ClExpr(); + + /** + * Construct a classical expression from an operation and its arguments + * + * @param op Operation + * @param args Arguments + */ + ClExpr(ClOp op, std::vector args); + + bool operator==(const ClExpr& other) const; + + friend std::ostream& operator<<(std::ostream& os, const ClExpr& expr); + + /** + * Main operation + */ + ClOp get_op() const; + + /** + * Arguments + */ + std::vector get_args() const; + + /** + * All bit variables occurring within the expression + */ + std::set all_bit_variables() const; + + /** + * All register variables occurring within the expression + */ + std::set all_reg_variables() const; + + private: + ClOp op; + std::vector args; + std::set all_bit_vars; + std::set all_reg_vars; +}; + +void to_json(nlohmann::json& j, const ClExpr& expr); + +void from_json(const nlohmann::json& j, ClExpr& expr); + +/** + * A classical expression defined over a sequence of bits + * + * This defines an operation on a finite number of bits. Bit variables within + * the expression are mapped to specific bit indices and register variables are + * mapped to specific (disjoint) sequences of bit indices. The output of the + * expression is also mapped to a specific bit index or sequence of bit indices. + * If the output is a register, it must either be disjoint from all of the input + * registers or exactly match one of them. + */ +class WiredClExpr { + public: + /** + * Default constructor + */ + WiredClExpr(); + + /** + * Construct by specifying the bit, register and output positions + * + * @param expr Expression + * @param bit_posn Map from identifiers of bit variables to bit positions + * @param reg_posn Map from identifiers of register variables to sequences of + * bit positions. + * @param output_posn Sequence of bit positions for the output + * @throws ClExprWiringError if wiring is not valid + */ + WiredClExpr( + const ClExpr& expr, const std::map& bit_posn, + const std::map>& reg_posn, + const std::vector output_posn); + + bool operator==(const WiredClExpr& other) const; + + friend std::ostream& operator<<(std::ostream& os, const WiredClExpr& expr); + + /** + * Expression + */ + ClExpr get_expr() const; + + /** + * Bit positions + */ + std::map get_bit_posn() const; + + /** + * Register positions + */ + std::map> get_reg_posn() const; + + /** + * Output positions + */ + std::vector get_output_posn() const; + + /** + * Total number of bits including bit and register inputs and output + */ + unsigned get_total_n_bits() const; + + private: + ClExpr expr; + std::map bit_posn; + std::map> reg_posn; + std::set all_bit_posns; + std::set> all_reg_posns; + std::vector output_posn; + unsigned total_n_bits; +}; + +void to_json(nlohmann::json& j, const WiredClExpr& expr); + +void from_json(const nlohmann::json& j, WiredClExpr& expr); + +class ClExprWiringError : public std::logic_error { + public: + explicit ClExprWiringError(const std::string& message) + : std::logic_error(message) {} +}; + +class ClExprOp : public Op { + public: + ClExprOp(const WiredClExpr& expr); + + Op_ptr symbol_substitution( + const SymEngine::map_basic_basic& sub_map) const override; + SymSet free_symbols() const override; + op_signature_t get_signature() const override; + + /** + * Wired classical expression + */ + WiredClExpr get_wired_expr() const; + + nlohmann::json serialize() const override; + + static Op_ptr deserialize(const nlohmann::json& j); + + private: + WiredClExpr expr; +}; + +} // namespace tket diff --git a/tket/include/tket/Predicates/PassGenerators.hpp b/tket/include/tket/Predicates/PassGenerators.hpp index 6573a02e21..9e7f87931c 100644 --- a/tket/include/tket/Predicates/PassGenerators.hpp +++ b/tket/include/tket/Predicates/PassGenerators.hpp @@ -350,9 +350,16 @@ PassPtr gen_special_UCC_synthesis( * * @param discount_rate * @param depth_weight + * @param max_lookahead + * @param max_tqe_candidates + * @param seed + * @param allow_zzphase * @return PassPtr */ -PassPtr gen_greedy_pauli_simp(double discount_rate, double depth_weight); +PassPtr gen_greedy_pauli_simp( + double discount_rate = 0.7, double depth_weight = 0.3, + unsigned max_lookahead = 500, unsigned max_tqe_candidates = 500, + unsigned seed = 0, bool allow_zzphase = false); /** * Generate a pass to simplify the circuit where it acts on known basis states. diff --git a/tket/include/tket/Transformations/GreedyPauliOptimisation.hpp b/tket/include/tket/Transformations/GreedyPauliOptimisation.hpp index 1a7cdbbf0f..de6bb19d5c 100644 --- a/tket/include/tket/Transformations/GreedyPauliOptimisation.hpp +++ b/tket/include/tket/Transformations/GreedyPauliOptimisation.hpp @@ -16,6 +16,7 @@ #include "Transform.hpp" #include "tket/Circuit/Circuit.hpp" +#include "tket/Clifford/UnitaryTableau.hpp" namespace tket { @@ -23,6 +24,12 @@ namespace Transforms { namespace GreedyPauliSimp { +class GreedyPauliSimpError : public std::logic_error { + public: + explicit GreedyPauliSimpError(const std::string& message) + : std::logic_error(message) {} +}; + /** * @brief Types of 2-qubit entangled Clifford gates * @@ -39,49 +46,116 @@ enum class TQEType : unsigned { ZZ, }; +enum class PauliNodeType { + // Pauli rotation + PauliRotation, + // Defines how a Pauli X and a Pauli Z on the same qubit + // get propagated from right to left through a Clifford operator. + PauliPropagation, + // Conditional Pauli rotations + ConditionalBlock, + // Classical operation + ClassicalNode, + // Mid-circuit measurement + MidMeasure, + // Reset + Reset, +}; + /** - * @brief Local Clifford + * @brief The type of a pair of Pauli letters defined by + their commutation relation * */ -enum class LocalCliffordType { - H, - S, - V, +enum class CommuteType : unsigned { + // Both are (I)dentity + I, + // (A)nti-commute + A, + // (C)ommute and not both identity + C, +}; + +enum class BitType : unsigned { + READ, + WRITE, +}; + +/** + * @brief Struct for 2-qubit entangled Clifford gates + * + */ +struct TQE { + TQEType type; + unsigned a; + unsigned b; + bool operator<(const TQE& other) const { + return std::tie(type, a, b) < std::tie(other.type, other.a, other.b); + } +}; + +/** + * @brief Struct for 2-qubit rotation gates + * + */ +struct Rotation2Q { + Pauli p_a; + Pauli p_b; + unsigned a; + unsigned b; + Expr angle; + unsigned index; + bool operator<(const Rotation2Q& other) const { return index < other.index; } +}; + +/** + * @brief Commutation information of a node specified by a list of + * Pauli strings along with classical READs and WRITEs. + */ +struct CommuteInfo { + std::vector> paulis; + // We use UnitID to differentiate between Bit and WasmState + std::vector> bits_info; }; /** - * @brief Type for 2-qubit entangled Clifford gates + * @brief Base class for nodes in the Greedy Pauli graph * */ -using TQE = std::tuple; +class PauliNode { + public: + virtual PauliNodeType get_type() const = 0; + virtual unsigned tqe_cost() const = 0; + virtual int tqe_cost_increase(const TQE& tqe) const = 0; + virtual void update(const TQE& tqe) = 0; + virtual void update(const OpType& sq_cliff, const unsigned& a); + virtual void swap(const unsigned& a, const unsigned& b); + virtual CommuteInfo get_commute_info() const = 0; + virtual std::vector reduction_tqes() const = 0; + virtual ~PauliNode(); +}; + +typedef std::shared_ptr PauliNode_ptr; /** - * @brief A Pauli exponential described by its commutation relations - * with the rows in a reference Clifford tableau. - * We store the commutation relations using an n-dimensional - * vector with entries in {0,1,2,3}, where - * 0: commute with ith Z row and ith X row - * 1: commute with ith Z row and anti-commute with ith X row - * 2: anti-commute with ith Z row and commute with ith X row - * 3: anti-commute with ith Z row and anti-commute with ith X row - * We call such vector a support vector + * @brief Base class for nodes defined by a single Pauli string */ -class PauliExpNode { +class SingleNode : public PauliNode { public: /** - * @brief Construct a new PauliExpNode object. + * @brief Construct a new SinglePauliNode object. * - * @param support_vec the support vector - * @param theta the rotation angle in half-turns + * @param string the Pauli string + * @param sign sign of the Pauli string */ - PauliExpNode(std::vector support_vec, Expr theta); + SingleNode(std::vector string, bool sign); /** * @brief Number of TQEs required to reduce the weight to 1 * * @return unsigned */ - unsigned tqe_cost() const { return tqe_cost_; } + unsigned tqe_cost() const override; /** * @brief Number of TQEs would required to reduce the weight to 1 @@ -89,123 +163,462 @@ class PauliExpNode { * * @return unsigned */ - int tqe_cost_increase(const TQE& tqe) const; + int tqe_cost_increase(const TQE& tqe) const override; /** - * @brief Update the support vector with a TQE gate + * @brief Update the Pauli string with a TQE gate * * @param tqe */ - void update(const TQE& tqe); - - Expr theta() const { return theta_; }; + void update(const TQE& tqe) override; /** * @brief Return all possible TQE gates that will reduce the tqe cost by 1 * * @return std::vector> */ - std::vector reduction_tqes() const; + std::vector reduction_tqes() const override; /** - * @brief Return the index and value of the first support + * @brief Return the index and value of the first non-identity * - * @return std::pair + * @return std::pair */ - std::pair first_support() const; + std::pair first_support() const; - private: - std::vector support_vec_; - Expr theta_; - unsigned tqe_cost_; + bool sign() const { return sign_; }; + + const std::vector& string() const { return string_; }; + + protected: + std::vector string_; + bool sign_; + // extra cached data used by greedy synthesis + unsigned weight_; }; /** - * @brief Each row of a Clifford tableau consists a pair of anti-commuting - * Pauli strings (p0,p1). Similar to the PauliExpNode, such pairs can be - * alternatively described by their commutation relations with the rows in a - * reference Clifford tableau. Let Xi and Zi be the ith X row and the ith Z row - * in a reference Tableau T, then the commutation relation between (p0, p1) and - * the ith row of T is defined by how p0, p1 commute with Xi and Zi. That's 4 - * bits of information. We store such information using an n-dimensional vector - * with entries in {0,1,2,...,15}. The 4 bits from the most significant to the - * least are: f(p0, Xi), f(p0,Zi), f(q,Xi), f(q,Zi) where f(p,q)==1 if p,q - * anti-commute and 0 otherwise + * @brief Base class for nodes defined by a pair of anti-commuting Pauli strings */ -class TableauRowNode { +class ACPairNode : public PauliNode { public: /** - * @brief Construct a new TableauRowNode object. + * @brief Construct a new ACPairNode object * - * @param support_vec the support vector + * @param z_string + * @param x_string + * @param z_sign + * @param x_sign */ - TableauRowNode(std::vector support_vec); + ACPairNode( + std::vector z_string, std::vector x_string, bool z_sign, + bool x_sign); /** * @brief Number of TQEs required to reduce the weight to 1 * * @return unsigned */ - unsigned tqe_cost() const { return tqe_cost_; }; + unsigned tqe_cost() const override; /** - * @brief Number of TQEs would required to reduce the weight to 1 + * @brief Number of additional TQEs would required to reduce the weight to 1 * after the given TQE is applied * - * @return unsigned + * @param tqe + * @return int */ - int tqe_cost_increase(const TQE& tqe) const; + int tqe_cost_increase(const TQE& tqe) const override; /** * @brief Update the support vector with a TQE gate * * @param tqe */ - void update(const TQE& tqe); + void update(const TQE& tqe) override; + + /** + * @brief Update the support vector with a single-qubit Clifford gate + * + * @param sq_cliff + * @param a + */ + void update(const OpType& sq_cliff, const unsigned& a) override; + + /** + * @brief Update the support vector with a SWAP gate + * + * @param a + * @param b + */ + void swap(const unsigned& a, const unsigned& b) override; /** * @brief Return all possible TQE gates that will reduce the tqe cost * - * @return std::vector> + * @return std::vector + */ + std::vector reduction_tqes() const override; + + /** + * @brief Return the index and value of the first anti-commute entry + */ + std::tuple first_support() const; + + bool z_sign() const { return z_sign_; }; + + bool x_sign() const { return x_sign_; }; + + const std::vector& z_string() const { return z_string_; }; + + const std::vector& x_string() const { return x_string_; }; + + protected: + std::vector z_string_; + std::vector x_string_; + bool z_sign_; + bool x_sign_; + // extra cached data used by greedy synthesis + std::vector commute_type_vec_; + unsigned n_commute_entries_; + unsigned n_anti_commute_entries_; +}; + +/** + * @brief Black box node for classical Ops + */ +class ClassicalNode : public PauliNode { + public: + ClassicalNode(std::vector args, Op_ptr op); + + PauliNodeType get_type() const override { + return PauliNodeType::ClassicalNode; + }; + + unsigned tqe_cost() const override { return 0; }; + int tqe_cost_increase(const TQE& /*tqe*/) const override { return 0; }; + void update(const TQE& /*tqe*/) override { return; }; + std::vector reduction_tqes() const override { return {}; }; + std::vector args() const { return args_; }; + Op_ptr op() const { return op_; }; + + CommuteInfo get_commute_info() const override; + + protected: + const std::vector args_; + const Op_ptr op_; +}; + +/** + * @brief A Pauli exponential defined by a dense Pauli string + * and a rotation angle + */ +class PauliRotation : public SingleNode { + public: + /** + * @brief Construct a new PauliRotation object. + * + * @param string the Pauli string + * @param sign the sign of the Pauli string + * @param theta the rotation angle in half-turns + */ + PauliRotation(std::vector string, bool sign, Expr theta); + + PauliNodeType get_type() const override { + return PauliNodeType::PauliRotation; + }; + + Expr angle() const { return sign_ ? theta_ : -theta_; }; + + CommuteInfo get_commute_info() const override; + + protected: + const Expr theta_; +}; + +/** + * @brief Measurement that has quantum or classical successors + */ +class MidMeasure : public SingleNode { + public: + /** + * @brief Construct a new Mid Measure object + * + * @param string dense Pauli string + * @param sign the sign of the Pauli string + * @param bit readout bit + */ + MidMeasure(std::vector string, bool sign, unsigned bit); + + PauliNodeType get_type() const override { return PauliNodeType::MidMeasure; }; + CommuteInfo get_commute_info() const override; + unsigned bit() const { return bit_; }; + + protected: + const unsigned bit_; +}; + +/** + * @brief Conditional block for rotations + */ +class ConditionalBlock : public PauliNode { + public: + /** + * @brief Construct a new Conditional Block object + * + * @param rotations Pauli rotations + * @param cond_bits conditional bits + * @param cond_value conditional value + */ + ConditionalBlock( + std::vector, bool, Expr>> rotations, + std::vector cond_bits, unsigned cond_value); + + /** + * @brief Sum of tqe_cost for each Pauli rotation + * + * @return unsigned + */ + unsigned tqe_cost() const override; + + /** + * @brief Sum of tqe_cost for each Pauli rotation after the given TQE is + * applied + * + * @param tqe + * @return unsigned + */ + int tqe_cost_increase(const TQE& tqe) const override; + + /** + * @brief Update the all Pauli rotations with the given TQE + * + * @param tqe */ - std::vector reduction_tqes() const; + void update(const TQE& tqe) override; + std::vector reduction_tqes() const override { return {}; }; + + std::vector cond_bits() const { return cond_bits_; }; + unsigned cond_value() const { return cond_value_; }; + + PauliNodeType get_type() const override { + return PauliNodeType::ConditionalBlock; + }; + + CommuteInfo get_commute_info() const override; + + void append(const ConditionalBlock& other); + + const std::vector, bool, Expr>>& rotations() + const { + return rotations_; + }; + + protected: + std::vector, bool, Expr>> rotations_; + const std::vector cond_bits_; + const unsigned cond_value_; + // extra cached data used by greedy synthesis + unsigned total_weight_; +}; + +/** + * @brief Defines how a Pauli X and a Pauli Z on the same qubit + * get propagated from right to left through a Clifford operator. + * A n-qubit Clifford operator is completely defined by n such propagations + * with one on each qubit. A PauliPropagation also corresponds to a row in + * a Clifford tableau + */ +class PauliPropagation : public ACPairNode { + public: /** - * @brief Return the index and value of the first support + * @brief Construct a new PauliPropagation object + * + * @param z_string propagated Pauli Z + * @param x_string propagated Pauli X + * @param z_sign the sign of z_string + * @param x_sign the sign of x_string + * @param qubit_index i.e. row index */ - std::pair first_support() const; + PauliPropagation( + std::vector z_string, std::vector x_string, bool z_sign, + bool x_sign, unsigned qubit_index); + + PauliNodeType get_type() const override { + return PauliNodeType::PauliPropagation; + }; + + CommuteInfo get_commute_info() const override; + + unsigned qubit_index() const { return qubit_index_; }; private: - std::vector support_vec_; - unsigned n_weaks_; - unsigned n_strongs_; - unsigned tqe_cost_; + const unsigned qubit_index_; +}; + +/** + * @brief Reset operation defined by a pair of anti-commuting strings + * For example, a tket Reset OpType can be defined as a Z/X pair. The Pauli Z + * can be seen as a Z-basis measurement, and the Pauli X can be seen as the post + * measurement conditional X gate. + * + */ +class Reset : public ACPairNode { + public: + /** + * @brief Construct a new Reset object + * + * @param z_string + * @param x_string + * @param z_sign + * @param x_sign + */ + Reset( + std::vector z_string, std::vector x_string, bool z_sign, + bool x_sign); + + PauliNodeType get_type() const override { return PauliNodeType::Reset; }; + CommuteInfo get_commute_info() const override; }; +typedef boost::adjacency_list< + boost::listS, boost::listS, boost::bidirectionalS, + // indexing needed for algorithms such as topological sort + boost::property> + GPDAG; + +typedef boost::graph_traits::vertex_descriptor GPVert; +typedef boost::graph_traits::edge_descriptor GPEdge; + +typedef sequence_set_t GPVertSet; +typedef sequence_set_t GPEdgeSet; + +typedef boost::adj_list_vertex_property_map< + GPDAG, int, int&, boost::vertex_index_t> + GPVIndex; + /** - * @brief The commutation relation between a TableauRowNode (p0,p1) and the ith - * row of the reference Tableau can be further classified as Strong, Weak or - * No-support. + * @brief Pauli graph structure for GreedyPauliSimp. + * + * A DAG is used to store all operations except for the end-of-circuit Clifford + * and end-of-circuit measurements. The vertices consist of Pauli rotations, + * mid-circuit measurements, resets, conditional Pauli rotations, and classical + * operations. Edges represent gate dependencies, where two nodes commute if + * they commute on both quantum and classical wires. + * + * - Quantum commutation: Nodes commute if all Pauli strings in one node + * commute with all strings in the other. + * - Classical commutation: Nodes commute if they do not share classical + * bits, or if they only read from shared bits. + * + * End-of-circuit measurements are stored as a map from integers to integers. + * These measurements are kept separate (i.e., after the final Clifford) so + * optimisation around them can later be handled by + * `CliffordPushThroughMeasures`. + * + * The final Clifford operator is stored using a `UnitaryRevTableau`. Note that + * `UnitaryRevTableau` is chosen over `PauliPropagations` due to the + * availability of existing update methods. */ -enum class SupportType : unsigned { - Strong, - Weak, - No, +class GPGraph { + public: + /** Construct an GPGraph from a circuit */ + GPGraph(const Circuit& circ); + + GPVertSet get_successors(const GPVert& vert) const; + + GPVertSet get_predecessors(const GPVert& vert) const; + + /** + * All vertices of the DAG, topologically sorted. + * + * This method is "morally" const, but it sets the vertex indices in the DAG. + * + * @return vector of vertices in a topological (causal) order + */ + std::vector vertices_in_order() const; + + std::tuple< + std::vector>, std::vector, + boost::bimap> + get_sequence(); + + private: + /** + * Applies the given gate to the end of the graph. + * Clifford gates transform the tableau. + * Non-Clifford gates and conditional Clifford gates are transformed + * into PauliNodes by the tableau and added + * to the graph. + */ + void apply_gate_at_end( + const Command& cmd, bool conditional = false, + std::vector cond_bits = {}, unsigned cond_value = 0); + + /** + * Add a Pauli rotation to the graph + * If the angle is non-Clifford or if conditional is true then add to the DAG + * as a PauliRotation node, otherwise update the tableau. + */ + void apply_paulis_at_end( + const std::vector, Expr>>& rotations, + const qubit_vector_t& qbs, bool conditional = false, + std::vector cond_bits = {}, unsigned cond_value = 0); + + /** + * Add a node to the DAG and check if it can be merged with another node. + */ + void apply_node_at_end(PauliNode_ptr& node); + + /** + * The dependency graph of Pauli nodes + * + * This is mutated by \ref vertices_in_order which indexes the vertices + * without changing the structure. + */ + mutable GPDAG graph_; + const unsigned n_qubits_; + const unsigned n_bits_; + + /** The tableau of the Clifford effect of the circuit */ + UnitaryRevTableau cliff_; + /** The record of measurements at the very end of the circuit */ + boost::bimap end_measures_; + + GPVertSet start_line_; + GPVertSet end_line_; }; /** - * @brief Given a circuit consists of PauliExpBoxes followed by clifford gates, - * and end-of-circuit measurements, implement the PauliExpBoxes and the final - * clifford subcircuit by applying Clifford gates and single qubit rotations in - * a greedy fashion. + * @brief Convert a unordered set of SymPauliTensor into a set of PauliRotations + * followed by a set of PauliPropagations + * + * @param unordered_set + * @return std::tuple, std::vector> + */ +std::tuple, std::vector> +gpg_from_unordered_set(const std::vector& unordered_set); + +/** + * @brief Converts the given circuit into a GPGraph and conjugates each node + * by greedily applying 2-qubit Clifford gates until the node can be realised + * as a single-qubit gate, a measurement, or a reset. The final Clifford + * operator is synthesized in a similar fashion. * * @param circ * @param discount_rate * @param depth_weight + * @param max_lookahead + * @param max_tqe_candidates + * @param seed + * @param allow_zzphase * @return Circuit */ Circuit greedy_pauli_graph_synthesis( - const Circuit& circ, double discount_rate = 0.7, double depth_weight = 0.3); + const Circuit& circ, double discount_rate = 0.7, double depth_weight = 0.3, + unsigned max_lookahead = 500, unsigned max_tqe_candidates = 500, + unsigned seed = 0, bool allow_zzphase = false); /** * @brief Synthesise a set of unordered Pauli exponentials by applying Clifford @@ -214,16 +627,23 @@ Circuit greedy_pauli_graph_synthesis( * * @param unordered_set * @param depth_weight + * @param max_lookahead + * @param max_tqe_candidates + * @param seed + * @param allow_zzphase * @return Circuit */ Circuit greedy_pauli_set_synthesis( - const std::vector& unordered_set, - double depth_weight = 0.3); + const std::vector& unordered_set, double depth_weight = 0.3, + unsigned max_lookahead = 500, unsigned max_tqe_candidates = 500, + unsigned seed = 0, bool allow_zzphase = false); } // namespace GreedyPauliSimp Transform greedy_pauli_optimisation( - double discount_rate = 0.7, double depth_weight = 0.3); + double discount_rate = 0.7, double depth_weight = 0.3, + unsigned max_lookahead = 500, unsigned max_tqe_candidates = 500, + unsigned seed = 0, bool allow_zzphase = false); } // namespace Transforms diff --git a/tket/include/tket/Transformations/GreedyPauliOptimisationLookupTables.hpp b/tket/include/tket/Transformations/GreedyPauliOptimisationLookupTables.hpp index 2445513636..a85a12b8bf 100644 --- a/tket/include/tket/Transformations/GreedyPauliOptimisationLookupTables.hpp +++ b/tket/include/tket/Transformations/GreedyPauliOptimisationLookupTables.hpp @@ -22,1545 +22,563 @@ namespace Transforms { namespace GreedyPauliSimp { -struct hash_tuple { - size_t operator()(const std::tuple& t) const { - return static_cast(std::get<0>(t)) * 10000 + - (std::get<1>(t) + 1) * 100 + std::get<2>(t); +struct hash_pauli_pauli { + size_t operator()(const std::pair& pair) const { + return pair.first * 10 + pair.second; } }; - -struct hash_pair { - size_t operator()(const std::pair& pair) const { - return pair.first * 100 + pair.second; +struct hash_optype_pauli { + size_t operator()(const std::pair& pair) const { + return static_cast(pair.first) * 10 + pair.second; + } +}; +struct hash_triple { + size_t operator()(const std::tuple& t) const { + return static_cast(std::get<0>(t)) * 100 + + (std::get<1>(t) + 1) * 10 + std::get<2>(t); + } +}; +struct hash_quadruple { + size_t operator()(const std::tuple& t) const { + return static_cast(std::get<0>(t)) * 1000 + + (std::get<1>(t) + 1) * 100 + (std::get<2>(t) + 1) * 10 + + std::get<3>(t); } }; /** - * @brief These are pre-calculated based on some property of the 2x2 matrix - * (i.e. local support matrix) defined by f(p0,Xi), f(p0,Zi), f(q,Xi), f(q,Zi) - * arxiv.org/abs/2305.10966 eq.27 + * @brief Transform a pair of anti-commuting pauli letters at the + * right-hand-side to Z/X For example, Sdg; H; X/Y = Z/X; Sdg; H */ -const static std::unordered_map FACTOR_WEAKNESS_MAP = { - {0, SupportType::No}, {1, SupportType::Weak}, - {2, SupportType::Weak}, {3, SupportType::Weak}, - {4, SupportType::Weak}, {5, SupportType::Weak}, - {6, SupportType::Strong}, {7, SupportType::Strong}, - {8, SupportType::Weak}, {9, SupportType::Strong}, - {10, SupportType::Weak}, {11, SupportType::Strong}, - {12, SupportType::Weak}, {13, SupportType::Strong}, - {14, SupportType::Strong}, {15, SupportType::Weak}}; +const static std::unordered_map< + const std::pair, std::vector, hash_pauli_pauli> + AA_TO_ZX = { + {{Pauli::X, Pauli::Y}, {OpType::Sdg, OpType::H}}, + {{Pauli::X, Pauli::Z}, {OpType::H}}, + {{Pauli::Y, Pauli::X}, {OpType::Vdg}}, + {{Pauli::Y, Pauli::Z}, {OpType::H, OpType::S}}, + {{Pauli::Z, Pauli::X}, {}}, + {{Pauli::Z, Pauli::Y}, {OpType::S}}}; -/** - * @brief Given a strong support in a factor support vector, returns the local - * clifford gates that turn it into an identity (i.e. 9). - */ -const static std::unordered_map> - FACTOR_STRONG_TO_LOCALS = { - {9, {}}, - {6, {LocalCliffordType::H}}, - {11, {LocalCliffordType::S}}, - {13, {LocalCliffordType::V}}, - {14, {LocalCliffordType::S, LocalCliffordType::H}}, - {7, {LocalCliffordType::H, LocalCliffordType::S}}, -}; +const static std::unordered_map SQ_CLIFF_DAGGER = { + {OpType::H, OpType::H}, + {OpType::S, OpType::Sdg}, + {OpType::Sdg, OpType::S}, + {OpType::V, OpType::Vdg}, + {OpType::Vdg, OpType::V}}; /** - * @brief Transform a pair of entries in a singlet support vector using a TQE + * @brief Given a SQ Clifford gate g and a Pauli operator P, return Pauli + * P', and sign k such that g;P; = k* P';g + * */ const static std::unordered_map< - std::tuple, std::pair, - hash_tuple> - SINGLET_PAIR_TRANSFORMATION_MAP = { - {{TQEType::XX, 2, 2}, {2, 2}}, {{TQEType::XY, 2, 2}, {0, 2}}, - {{TQEType::XZ, 2, 2}, {0, 2}}, {{TQEType::YX, 2, 2}, {2, 0}}, - {{TQEType::YY, 2, 2}, {1, 1}}, {{TQEType::YZ, 2, 2}, {1, 3}}, - {{TQEType::ZX, 2, 2}, {2, 0}}, {{TQEType::ZY, 2, 2}, {3, 1}}, - {{TQEType::ZZ, 2, 2}, {3, 3}}, {{TQEType::XX, 3, 2}, {3, 0}}, - {{TQEType::XY, 3, 2}, {1, 1}}, {{TQEType::XZ, 3, 2}, {1, 3}}, - {{TQEType::YX, 3, 2}, {3, 2}}, {{TQEType::YY, 3, 2}, {0, 2}}, - {{TQEType::YZ, 3, 2}, {0, 2}}, {{TQEType::ZX, 3, 2}, {3, 0}}, - {{TQEType::ZY, 3, 2}, {2, 1}}, {{TQEType::ZZ, 3, 2}, {2, 3}}, - {{TQEType::XX, 1, 2}, {1, 0}}, {{TQEType::XY, 1, 2}, {3, 1}}, - {{TQEType::XZ, 1, 2}, {3, 3}}, {{TQEType::YX, 1, 2}, {1, 0}}, - {{TQEType::YY, 1, 2}, {2, 1}}, {{TQEType::YZ, 1, 2}, {2, 3}}, - {{TQEType::ZX, 1, 2}, {1, 2}}, {{TQEType::ZY, 1, 2}, {0, 2}}, - {{TQEType::ZZ, 1, 2}, {0, 2}}, {{TQEType::XX, 0, 2}, {0, 2}}, - {{TQEType::XY, 0, 2}, {2, 2}}, {{TQEType::XZ, 0, 2}, {2, 2}}, - {{TQEType::YX, 0, 2}, {0, 2}}, {{TQEType::YY, 0, 2}, {3, 2}}, - {{TQEType::YZ, 0, 2}, {3, 2}}, {{TQEType::ZX, 0, 2}, {0, 2}}, - {{TQEType::ZY, 0, 2}, {1, 2}}, {{TQEType::ZZ, 0, 2}, {1, 2}}, - {{TQEType::XX, 2, 3}, {0, 3}}, {{TQEType::XY, 2, 3}, {2, 3}}, - {{TQEType::XZ, 2, 3}, {0, 3}}, {{TQEType::YX, 2, 3}, {1, 1}}, - {{TQEType::YY, 2, 3}, {2, 0}}, {{TQEType::YZ, 2, 3}, {1, 2}}, - {{TQEType::ZX, 2, 3}, {3, 1}}, {{TQEType::ZY, 2, 3}, {2, 0}}, - {{TQEType::ZZ, 2, 3}, {3, 2}}, {{TQEType::XX, 3, 3}, {1, 1}}, - {{TQEType::XY, 3, 3}, {3, 0}}, {{TQEType::XZ, 3, 3}, {1, 2}}, - {{TQEType::YX, 3, 3}, {0, 3}}, {{TQEType::YY, 3, 3}, {3, 3}}, - {{TQEType::YZ, 3, 3}, {0, 3}}, {{TQEType::ZX, 3, 3}, {2, 1}}, - {{TQEType::ZY, 3, 3}, {3, 0}}, {{TQEType::ZZ, 3, 3}, {2, 2}}, - {{TQEType::XX, 1, 3}, {3, 1}}, {{TQEType::XY, 1, 3}, {1, 0}}, - {{TQEType::XZ, 1, 3}, {3, 2}}, {{TQEType::YX, 1, 3}, {2, 1}}, - {{TQEType::YY, 1, 3}, {1, 0}}, {{TQEType::YZ, 1, 3}, {2, 2}}, - {{TQEType::ZX, 1, 3}, {0, 3}}, {{TQEType::ZY, 1, 3}, {1, 3}}, - {{TQEType::ZZ, 1, 3}, {0, 3}}, {{TQEType::XX, 0, 3}, {2, 3}}, - {{TQEType::XY, 0, 3}, {0, 3}}, {{TQEType::XZ, 0, 3}, {2, 3}}, - {{TQEType::YX, 0, 3}, {3, 3}}, {{TQEType::YY, 0, 3}, {0, 3}}, - {{TQEType::YZ, 0, 3}, {3, 3}}, {{TQEType::ZX, 0, 3}, {1, 3}}, - {{TQEType::ZY, 0, 3}, {0, 3}}, {{TQEType::ZZ, 0, 3}, {1, 3}}, - {{TQEType::XX, 2, 1}, {0, 1}}, {{TQEType::XY, 2, 1}, {0, 1}}, - {{TQEType::XZ, 2, 1}, {2, 1}}, {{TQEType::YX, 2, 1}, {1, 3}}, - {{TQEType::YY, 2, 1}, {1, 2}}, {{TQEType::YZ, 2, 1}, {2, 0}}, - {{TQEType::ZX, 2, 1}, {3, 3}}, {{TQEType::ZY, 2, 1}, {3, 2}}, - {{TQEType::ZZ, 2, 1}, {2, 0}}, {{TQEType::XX, 3, 1}, {1, 3}}, - {{TQEType::XY, 3, 1}, {1, 2}}, {{TQEType::XZ, 3, 1}, {3, 0}}, - {{TQEType::YX, 3, 1}, {0, 1}}, {{TQEType::YY, 3, 1}, {0, 1}}, - {{TQEType::YZ, 3, 1}, {3, 1}}, {{TQEType::ZX, 3, 1}, {2, 3}}, - {{TQEType::ZY, 3, 1}, {2, 2}}, {{TQEType::ZZ, 3, 1}, {3, 0}}, - {{TQEType::XX, 1, 1}, {3, 3}}, {{TQEType::XY, 1, 1}, {3, 2}}, - {{TQEType::XZ, 1, 1}, {1, 0}}, {{TQEType::YX, 1, 1}, {2, 3}}, - {{TQEType::YY, 1, 1}, {2, 2}}, {{TQEType::YZ, 1, 1}, {1, 0}}, - {{TQEType::ZX, 1, 1}, {0, 1}}, {{TQEType::ZY, 1, 1}, {0, 1}}, - {{TQEType::ZZ, 1, 1}, {1, 1}}, {{TQEType::XX, 0, 1}, {2, 1}}, - {{TQEType::XY, 0, 1}, {2, 1}}, {{TQEType::XZ, 0, 1}, {0, 1}}, - {{TQEType::YX, 0, 1}, {3, 1}}, {{TQEType::YY, 0, 1}, {3, 1}}, - {{TQEType::YZ, 0, 1}, {0, 1}}, {{TQEType::ZX, 0, 1}, {1, 1}}, - {{TQEType::ZY, 0, 1}, {1, 1}}, {{TQEType::ZZ, 0, 1}, {0, 1}}, - {{TQEType::XX, 2, 0}, {2, 0}}, {{TQEType::XY, 2, 0}, {2, 0}}, - {{TQEType::XZ, 2, 0}, {2, 0}}, {{TQEType::YX, 2, 0}, {2, 2}}, - {{TQEType::YY, 2, 0}, {2, 3}}, {{TQEType::YZ, 2, 0}, {2, 1}}, - {{TQEType::ZX, 2, 0}, {2, 2}}, {{TQEType::ZY, 2, 0}, {2, 3}}, - {{TQEType::ZZ, 2, 0}, {2, 1}}, {{TQEType::XX, 3, 0}, {3, 2}}, - {{TQEType::XY, 3, 0}, {3, 3}}, {{TQEType::XZ, 3, 0}, {3, 1}}, - {{TQEType::YX, 3, 0}, {3, 0}}, {{TQEType::YY, 3, 0}, {3, 0}}, - {{TQEType::YZ, 3, 0}, {3, 0}}, {{TQEType::ZX, 3, 0}, {3, 2}}, - {{TQEType::ZY, 3, 0}, {3, 3}}, {{TQEType::ZZ, 3, 0}, {3, 1}}, - {{TQEType::XX, 1, 0}, {1, 2}}, {{TQEType::XY, 1, 0}, {1, 3}}, - {{TQEType::XZ, 1, 0}, {1, 1}}, {{TQEType::YX, 1, 0}, {1, 2}}, - {{TQEType::YY, 1, 0}, {1, 3}}, {{TQEType::YZ, 1, 0}, {1, 1}}, - {{TQEType::ZX, 1, 0}, {1, 0}}, {{TQEType::ZY, 1, 0}, {1, 0}}, - {{TQEType::ZZ, 1, 0}, {1, 0}}, {{TQEType::XX, 0, 0}, {0, 0}}, - {{TQEType::XY, 0, 0}, {0, 0}}, {{TQEType::XZ, 0, 0}, {0, 0}}, - {{TQEType::YX, 0, 0}, {0, 0}}, {{TQEType::YY, 0, 0}, {0, 0}}, - {{TQEType::YZ, 0, 0}, {0, 0}}, {{TQEType::ZX, 0, 0}, {0, 0}}, - {{TQEType::ZY, 0, 0}, {0, 0}}, {{TQEType::ZZ, 0, 0}, {0, 0}}}; + std::pair, std::pair, hash_optype_pauli> + SQ_CLIFF_MAP = { + {{OpType::H, Pauli::X}, {Pauli::Z, true}}, + {{OpType::S, Pauli::X}, {Pauli::Y, false}}, + {{OpType::Sdg, Pauli::X}, {Pauli::Y, true}}, + {{OpType::V, Pauli::X}, {Pauli::X, true}}, + {{OpType::Vdg, Pauli::X}, {Pauli::X, true}}, + {{OpType::X, Pauli::X}, {Pauli::X, true}}, + {{OpType::Y, Pauli::X}, {Pauli::X, false}}, + {{OpType::Z, Pauli::X}, {Pauli::X, false}}, + {{OpType::H, Pauli::Y}, {Pauli::Y, false}}, + {{OpType::S, Pauli::Y}, {Pauli::X, true}}, + {{OpType::Sdg, Pauli::Y}, {Pauli::X, false}}, + {{OpType::V, Pauli::Y}, {Pauli::Z, false}}, + {{OpType::Vdg, Pauli::Y}, {Pauli::Z, true}}, + {{OpType::X, Pauli::Y}, {Pauli::Y, false}}, + {{OpType::Y, Pauli::Y}, {Pauli::Y, true}}, + {{OpType::Z, Pauli::Y}, {Pauli::Y, false}}, + {{OpType::H, Pauli::Z}, {Pauli::X, true}}, + {{OpType::S, Pauli::Z}, {Pauli::Z, true}}, + {{OpType::Sdg, Pauli::Z}, {Pauli::Z, true}}, + {{OpType::V, Pauli::Z}, {Pauli::Y, true}}, + {{OpType::Vdg, Pauli::Z}, {Pauli::Y, false}}, + {{OpType::X, Pauli::Z}, {Pauli::Z, false}}, + {{OpType::Y, Pauli::Z}, {Pauli::Z, false}}, + {{OpType::Z, Pauli::Z}, {Pauli::Z, true}}}; /** - * @brief Maps a pair of non-zero entires in a singlet support vector - * to a set of 4 TQE gates that will reduce one of them to 0 + * @brief Given TQE;P(0);Q(1), return P'(0), Q'(0), and sign k such that + * TQE;P(0);Q(1) = k* P'(0);Q'(1);TQE */ const static std::unordered_map< - std::pair, std::vector, hash_pair> - SINGLET_PAIR_REDUCTION_TQES = { - {{2, 2}, {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::ZX}}, - {{3, 2}, {TQEType::XX, TQEType::YY, TQEType::YZ, TQEType::ZX}}, - {{1, 2}, {TQEType::XX, TQEType::YX, TQEType::ZY, TQEType::ZZ}}, - {{2, 3}, {TQEType::XX, TQEType::XZ, TQEType::YY, TQEType::ZY}}, - {{3, 3}, {TQEType::XY, TQEType::YX, TQEType::YZ, TQEType::ZY}}, - {{1, 3}, {TQEType::XY, TQEType::YY, TQEType::ZX, TQEType::ZZ}}, - {{2, 1}, {TQEType::XX, TQEType::XY, TQEType::YZ, TQEType::ZZ}}, - {{3, 1}, {TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZZ}}, - {{1, 1}, {TQEType::XZ, TQEType::YZ, TQEType::ZX, TQEType::ZY}}}; + std::tuple, std::tuple, + hash_triple> + TQE_PAULI_MAP = { + {{TQEType::XX, Pauli::X, Pauli::X}, {Pauli::X, Pauli::X, true}}, + {{TQEType::XY, Pauli::X, Pauli::X}, {Pauli::I, Pauli::X, true}}, + {{TQEType::XZ, Pauli::X, Pauli::X}, {Pauli::I, Pauli::X, true}}, + {{TQEType::YX, Pauli::X, Pauli::X}, {Pauli::X, Pauli::I, true}}, + {{TQEType::YY, Pauli::X, Pauli::X}, {Pauli::Z, Pauli::Z, true}}, + {{TQEType::YZ, Pauli::X, Pauli::X}, {Pauli::Z, Pauli::Y, false}}, + {{TQEType::ZX, Pauli::X, Pauli::X}, {Pauli::X, Pauli::I, true}}, + {{TQEType::ZY, Pauli::X, Pauli::X}, {Pauli::Y, Pauli::Z, false}}, + {{TQEType::ZZ, Pauli::X, Pauli::X}, {Pauli::Y, Pauli::Y, true}}, + {{TQEType::XX, Pauli::X, Pauli::Y}, {Pauli::I, Pauli::Y, true}}, + {{TQEType::XY, Pauli::X, Pauli::Y}, {Pauli::X, Pauli::Y, true}}, + {{TQEType::XZ, Pauli::X, Pauli::Y}, {Pauli::I, Pauli::Y, true}}, + {{TQEType::YX, Pauli::X, Pauli::Y}, {Pauli::Z, Pauli::Z, false}}, + {{TQEType::YY, Pauli::X, Pauli::Y}, {Pauli::X, Pauli::I, true}}, + {{TQEType::YZ, Pauli::X, Pauli::Y}, {Pauli::Z, Pauli::X, true}}, + {{TQEType::ZX, Pauli::X, Pauli::Y}, {Pauli::Y, Pauli::Z, true}}, + {{TQEType::ZY, Pauli::X, Pauli::Y}, {Pauli::X, Pauli::I, true}}, + {{TQEType::ZZ, Pauli::X, Pauli::Y}, {Pauli::Y, Pauli::X, false}}, + {{TQEType::XX, Pauli::X, Pauli::Z}, {Pauli::I, Pauli::Z, true}}, + {{TQEType::XY, Pauli::X, Pauli::Z}, {Pauli::I, Pauli::Z, true}}, + {{TQEType::XZ, Pauli::X, Pauli::Z}, {Pauli::X, Pauli::Z, true}}, + {{TQEType::YX, Pauli::X, Pauli::Z}, {Pauli::Z, Pauli::Y, true}}, + {{TQEType::YY, Pauli::X, Pauli::Z}, {Pauli::Z, Pauli::X, false}}, + {{TQEType::YZ, Pauli::X, Pauli::Z}, {Pauli::X, Pauli::I, true}}, + {{TQEType::ZX, Pauli::X, Pauli::Z}, {Pauli::Y, Pauli::Y, false}}, + {{TQEType::ZY, Pauli::X, Pauli::Z}, {Pauli::Y, Pauli::X, true}}, + {{TQEType::ZZ, Pauli::X, Pauli::Z}, {Pauli::X, Pauli::I, true}}, + {{TQEType::XX, Pauli::X, Pauli::I}, {Pauli::X, Pauli::I, true}}, + {{TQEType::XY, Pauli::X, Pauli::I}, {Pauli::X, Pauli::I, true}}, + {{TQEType::XZ, Pauli::X, Pauli::I}, {Pauli::X, Pauli::I, true}}, + {{TQEType::YX, Pauli::X, Pauli::I}, {Pauli::X, Pauli::X, true}}, + {{TQEType::YY, Pauli::X, Pauli::I}, {Pauli::X, Pauli::Y, true}}, + {{TQEType::YZ, Pauli::X, Pauli::I}, {Pauli::X, Pauli::Z, true}}, + {{TQEType::ZX, Pauli::X, Pauli::I}, {Pauli::X, Pauli::X, true}}, + {{TQEType::ZY, Pauli::X, Pauli::I}, {Pauli::X, Pauli::Y, true}}, + {{TQEType::ZZ, Pauli::X, Pauli::I}, {Pauli::X, Pauli::Z, true}}, + {{TQEType::XX, Pauli::Y, Pauli::X}, {Pauli::Y, Pauli::I, true}}, + {{TQEType::XY, Pauli::Y, Pauli::X}, {Pauli::Z, Pauli::Z, false}}, + {{TQEType::XZ, Pauli::Y, Pauli::X}, {Pauli::Z, Pauli::Y, true}}, + {{TQEType::YX, Pauli::Y, Pauli::X}, {Pauli::Y, Pauli::X, true}}, + {{TQEType::YY, Pauli::Y, Pauli::X}, {Pauli::I, Pauli::X, true}}, + {{TQEType::YZ, Pauli::Y, Pauli::X}, {Pauli::I, Pauli::X, true}}, + {{TQEType::ZX, Pauli::Y, Pauli::X}, {Pauli::Y, Pauli::I, true}}, + {{TQEType::ZY, Pauli::Y, Pauli::X}, {Pauli::X, Pauli::Z, true}}, + {{TQEType::ZZ, Pauli::Y, Pauli::X}, {Pauli::X, Pauli::Y, false}}, + {{TQEType::XX, Pauli::Y, Pauli::Y}, {Pauli::Z, Pauli::Z, true}}, + {{TQEType::XY, Pauli::Y, Pauli::Y}, {Pauli::Y, Pauli::I, true}}, + {{TQEType::XZ, Pauli::Y, Pauli::Y}, {Pauli::Z, Pauli::X, false}}, + {{TQEType::YX, Pauli::Y, Pauli::Y}, {Pauli::I, Pauli::Y, true}}, + {{TQEType::YY, Pauli::Y, Pauli::Y}, {Pauli::Y, Pauli::Y, true}}, + {{TQEType::YZ, Pauli::Y, Pauli::Y}, {Pauli::I, Pauli::Y, true}}, + {{TQEType::ZX, Pauli::Y, Pauli::Y}, {Pauli::X, Pauli::Z, false}}, + {{TQEType::ZY, Pauli::Y, Pauli::Y}, {Pauli::Y, Pauli::I, true}}, + {{TQEType::ZZ, Pauli::Y, Pauli::Y}, {Pauli::X, Pauli::X, true}}, + {{TQEType::XX, Pauli::Y, Pauli::Z}, {Pauli::Z, Pauli::Y, false}}, + {{TQEType::XY, Pauli::Y, Pauli::Z}, {Pauli::Z, Pauli::X, true}}, + {{TQEType::XZ, Pauli::Y, Pauli::Z}, {Pauli::Y, Pauli::I, true}}, + {{TQEType::YX, Pauli::Y, Pauli::Z}, {Pauli::I, Pauli::Z, true}}, + {{TQEType::YY, Pauli::Y, Pauli::Z}, {Pauli::I, Pauli::Z, true}}, + {{TQEType::YZ, Pauli::Y, Pauli::Z}, {Pauli::Y, Pauli::Z, true}}, + {{TQEType::ZX, Pauli::Y, Pauli::Z}, {Pauli::X, Pauli::Y, true}}, + {{TQEType::ZY, Pauli::Y, Pauli::Z}, {Pauli::X, Pauli::X, false}}, + {{TQEType::ZZ, Pauli::Y, Pauli::Z}, {Pauli::Y, Pauli::I, true}}, + {{TQEType::XX, Pauli::Y, Pauli::I}, {Pauli::Y, Pauli::X, true}}, + {{TQEType::XY, Pauli::Y, Pauli::I}, {Pauli::Y, Pauli::Y, true}}, + {{TQEType::XZ, Pauli::Y, Pauli::I}, {Pauli::Y, Pauli::Z, true}}, + {{TQEType::YX, Pauli::Y, Pauli::I}, {Pauli::Y, Pauli::I, true}}, + {{TQEType::YY, Pauli::Y, Pauli::I}, {Pauli::Y, Pauli::I, true}}, + {{TQEType::YZ, Pauli::Y, Pauli::I}, {Pauli::Y, Pauli::I, true}}, + {{TQEType::ZX, Pauli::Y, Pauli::I}, {Pauli::Y, Pauli::X, true}}, + {{TQEType::ZY, Pauli::Y, Pauli::I}, {Pauli::Y, Pauli::Y, true}}, + {{TQEType::ZZ, Pauli::Y, Pauli::I}, {Pauli::Y, Pauli::Z, true}}, + {{TQEType::XX, Pauli::Z, Pauli::X}, {Pauli::Z, Pauli::I, true}}, + {{TQEType::XY, Pauli::Z, Pauli::X}, {Pauli::Y, Pauli::Z, true}}, + {{TQEType::XZ, Pauli::Z, Pauli::X}, {Pauli::Y, Pauli::Y, false}}, + {{TQEType::YX, Pauli::Z, Pauli::X}, {Pauli::Z, Pauli::I, true}}, + {{TQEType::YY, Pauli::Z, Pauli::X}, {Pauli::X, Pauli::Z, false}}, + {{TQEType::YZ, Pauli::Z, Pauli::X}, {Pauli::X, Pauli::Y, true}}, + {{TQEType::ZX, Pauli::Z, Pauli::X}, {Pauli::Z, Pauli::X, true}}, + {{TQEType::ZY, Pauli::Z, Pauli::X}, {Pauli::I, Pauli::X, true}}, + {{TQEType::ZZ, Pauli::Z, Pauli::X}, {Pauli::I, Pauli::X, true}}, + {{TQEType::XX, Pauli::Z, Pauli::Y}, {Pauli::Y, Pauli::Z, false}}, + {{TQEType::XY, Pauli::Z, Pauli::Y}, {Pauli::Z, Pauli::I, true}}, + {{TQEType::XZ, Pauli::Z, Pauli::Y}, {Pauli::Y, Pauli::X, true}}, + {{TQEType::YX, Pauli::Z, Pauli::Y}, {Pauli::X, Pauli::Z, true}}, + {{TQEType::YY, Pauli::Z, Pauli::Y}, {Pauli::Z, Pauli::I, true}}, + {{TQEType::YZ, Pauli::Z, Pauli::Y}, {Pauli::X, Pauli::X, false}}, + {{TQEType::ZX, Pauli::Z, Pauli::Y}, {Pauli::I, Pauli::Y, true}}, + {{TQEType::ZY, Pauli::Z, Pauli::Y}, {Pauli::Z, Pauli::Y, true}}, + {{TQEType::ZZ, Pauli::Z, Pauli::Y}, {Pauli::I, Pauli::Y, true}}, + {{TQEType::XX, Pauli::Z, Pauli::Z}, {Pauli::Y, Pauli::Y, true}}, + {{TQEType::XY, Pauli::Z, Pauli::Z}, {Pauli::Y, Pauli::X, false}}, + {{TQEType::XZ, Pauli::Z, Pauli::Z}, {Pauli::Z, Pauli::I, true}}, + {{TQEType::YX, Pauli::Z, Pauli::Z}, {Pauli::X, Pauli::Y, false}}, + {{TQEType::YY, Pauli::Z, Pauli::Z}, {Pauli::X, Pauli::X, true}}, + {{TQEType::YZ, Pauli::Z, Pauli::Z}, {Pauli::Z, Pauli::I, true}}, + {{TQEType::ZX, Pauli::Z, Pauli::Z}, {Pauli::I, Pauli::Z, true}}, + {{TQEType::ZY, Pauli::Z, Pauli::Z}, {Pauli::I, Pauli::Z, true}}, + {{TQEType::ZZ, Pauli::Z, Pauli::Z}, {Pauli::Z, Pauli::Z, true}}, + {{TQEType::XX, Pauli::Z, Pauli::I}, {Pauli::Z, Pauli::X, true}}, + {{TQEType::XY, Pauli::Z, Pauli::I}, {Pauli::Z, Pauli::Y, true}}, + {{TQEType::XZ, Pauli::Z, Pauli::I}, {Pauli::Z, Pauli::Z, true}}, + {{TQEType::YX, Pauli::Z, Pauli::I}, {Pauli::Z, Pauli::X, true}}, + {{TQEType::YY, Pauli::Z, Pauli::I}, {Pauli::Z, Pauli::Y, true}}, + {{TQEType::YZ, Pauli::Z, Pauli::I}, {Pauli::Z, Pauli::Z, true}}, + {{TQEType::ZX, Pauli::Z, Pauli::I}, {Pauli::Z, Pauli::I, true}}, + {{TQEType::ZY, Pauli::Z, Pauli::I}, {Pauli::Z, Pauli::I, true}}, + {{TQEType::ZZ, Pauli::Z, Pauli::I}, {Pauli::Z, Pauli::I, true}}, + {{TQEType::XX, Pauli::I, Pauli::X}, {Pauli::I, Pauli::X, true}}, + {{TQEType::XY, Pauli::I, Pauli::X}, {Pauli::X, Pauli::X, true}}, + {{TQEType::XZ, Pauli::I, Pauli::X}, {Pauli::X, Pauli::X, true}}, + {{TQEType::YX, Pauli::I, Pauli::X}, {Pauli::I, Pauli::X, true}}, + {{TQEType::YY, Pauli::I, Pauli::X}, {Pauli::Y, Pauli::X, true}}, + {{TQEType::YZ, Pauli::I, Pauli::X}, {Pauli::Y, Pauli::X, true}}, + {{TQEType::ZX, Pauli::I, Pauli::X}, {Pauli::I, Pauli::X, true}}, + {{TQEType::ZY, Pauli::I, Pauli::X}, {Pauli::Z, Pauli::X, true}}, + {{TQEType::ZZ, Pauli::I, Pauli::X}, {Pauli::Z, Pauli::X, true}}, + {{TQEType::XX, Pauli::I, Pauli::Y}, {Pauli::X, Pauli::Y, true}}, + {{TQEType::XY, Pauli::I, Pauli::Y}, {Pauli::I, Pauli::Y, true}}, + {{TQEType::XZ, Pauli::I, Pauli::Y}, {Pauli::X, Pauli::Y, true}}, + {{TQEType::YX, Pauli::I, Pauli::Y}, {Pauli::Y, Pauli::Y, true}}, + {{TQEType::YY, Pauli::I, Pauli::Y}, {Pauli::I, Pauli::Y, true}}, + {{TQEType::YZ, Pauli::I, Pauli::Y}, {Pauli::Y, Pauli::Y, true}}, + {{TQEType::ZX, Pauli::I, Pauli::Y}, {Pauli::Z, Pauli::Y, true}}, + {{TQEType::ZY, Pauli::I, Pauli::Y}, {Pauli::I, Pauli::Y, true}}, + {{TQEType::ZZ, Pauli::I, Pauli::Y}, {Pauli::Z, Pauli::Y, true}}, + {{TQEType::XX, Pauli::I, Pauli::Z}, {Pauli::X, Pauli::Z, true}}, + {{TQEType::XY, Pauli::I, Pauli::Z}, {Pauli::X, Pauli::Z, true}}, + {{TQEType::XZ, Pauli::I, Pauli::Z}, {Pauli::I, Pauli::Z, true}}, + {{TQEType::YX, Pauli::I, Pauli::Z}, {Pauli::Y, Pauli::Z, true}}, + {{TQEType::YY, Pauli::I, Pauli::Z}, {Pauli::Y, Pauli::Z, true}}, + {{TQEType::YZ, Pauli::I, Pauli::Z}, {Pauli::I, Pauli::Z, true}}, + {{TQEType::ZX, Pauli::I, Pauli::Z}, {Pauli::Z, Pauli::Z, true}}, + {{TQEType::ZY, Pauli::I, Pauli::Z}, {Pauli::Z, Pauli::Z, true}}, + {{TQEType::ZZ, Pauli::I, Pauli::Z}, {Pauli::I, Pauli::Z, true}}, + {{TQEType::XX, Pauli::I, Pauli::I}, {Pauli::I, Pauli::I, true}}, + {{TQEType::XY, Pauli::I, Pauli::I}, {Pauli::I, Pauli::I, true}}, + {{TQEType::XZ, Pauli::I, Pauli::I}, {Pauli::I, Pauli::I, true}}, + {{TQEType::YX, Pauli::I, Pauli::I}, {Pauli::I, Pauli::I, true}}, + {{TQEType::YY, Pauli::I, Pauli::I}, {Pauli::I, Pauli::I, true}}, + {{TQEType::YZ, Pauli::I, Pauli::I}, {Pauli::I, Pauli::I, true}}, + {{TQEType::ZX, Pauli::I, Pauli::I}, {Pauli::I, Pauli::I, true}}, + {{TQEType::ZY, Pauli::I, Pauli::I}, {Pauli::I, Pauli::I, true}}, + {{TQEType::ZZ, Pauli::I, Pauli::I}, {Pauli::I, Pauli::I, true}}}; /** - * @brief Transform a pair of entries in a factor support vector using a TQE + * @brief Given non-identities P(0), Q(0), + * return a list of TQEs, T, such that t;P(0);Q(1) = P'(0);Q'(1);t, + * for all t in T, and one of P'(0), Q'(1) is identity. */ const static std::unordered_map< - std::tuple, std::pair, - hash_tuple> - FACTOR_PAIR_TRANSFORMATION_MAP = { - {{TQEType::XX, 7, 5}, {7, 4}}, {{TQEType::XY, 7, 5}, {2, 6}}, - {{TQEType::XZ, 7, 5}, {2, 7}}, {{TQEType::YX, 7, 5}, {7, 1}}, - {{TQEType::YY, 7, 5}, {8, 9}}, {{TQEType::YZ, 7, 5}, {8, 13}}, - {{TQEType::ZX, 7, 5}, {7, 0}}, {{TQEType::ZY, 7, 5}, {13, 10}}, - {{TQEType::ZZ, 7, 5}, {13, 15}}, {{TQEType::XX, 6, 5}, {6, 4}}, - {{TQEType::XY, 6, 5}, {3, 6}}, {{TQEType::XZ, 6, 5}, {3, 7}}, - {{TQEType::YX, 6, 5}, {6, 0}}, {{TQEType::YY, 6, 5}, {9, 10}}, - {{TQEType::YZ, 6, 5}, {9, 15}}, {{TQEType::ZX, 6, 5}, {6, 1}}, - {{TQEType::ZY, 6, 5}, {12, 9}}, {{TQEType::ZZ, 6, 5}, {12, 13}}, - {{TQEType::XX, 5, 7}, {4, 7}}, {{TQEType::XY, 5, 7}, {1, 7}}, - {{TQEType::XZ, 5, 7}, {0, 7}}, {{TQEType::YX, 5, 7}, {6, 2}}, - {{TQEType::YY, 5, 7}, {9, 8}}, {{TQEType::YZ, 5, 7}, {10, 13}}, - {{TQEType::ZX, 5, 7}, {7, 2}}, {{TQEType::ZY, 5, 7}, {13, 8}}, - {{TQEType::ZZ, 5, 7}, {15, 13}}, {{TQEType::XX, 4, 7}, {5, 7}}, - {{TQEType::XY, 4, 7}, {0, 7}}, {{TQEType::XZ, 4, 7}, {1, 7}}, - {{TQEType::YX, 4, 7}, {7, 3}}, {{TQEType::YY, 4, 7}, {8, 11}}, - {{TQEType::YZ, 4, 7}, {11, 15}}, {{TQEType::ZX, 4, 7}, {6, 3}}, - {{TQEType::ZY, 4, 7}, {12, 11}}, {{TQEType::ZZ, 4, 7}, {14, 15}}, - {{TQEType::XX, 5, 6}, {4, 6}}, {{TQEType::XY, 5, 6}, {0, 6}}, - {{TQEType::XZ, 5, 6}, {1, 6}}, {{TQEType::YX, 5, 6}, {6, 3}}, - {{TQEType::YY, 5, 6}, {10, 9}}, {{TQEType::YZ, 5, 6}, {9, 12}}, - {{TQEType::ZX, 5, 6}, {7, 3}}, {{TQEType::ZY, 5, 6}, {15, 9}}, - {{TQEType::ZZ, 5, 6}, {13, 12}}, {{TQEType::XX, 4, 6}, {5, 6}}, - {{TQEType::XY, 4, 6}, {1, 6}}, {{TQEType::XZ, 4, 6}, {0, 6}}, - {{TQEType::YX, 4, 6}, {7, 2}}, {{TQEType::YY, 4, 6}, {11, 10}}, - {{TQEType::YZ, 4, 6}, {8, 14}}, {{TQEType::ZX, 4, 6}, {6, 2}}, - {{TQEType::ZY, 4, 6}, {14, 10}}, {{TQEType::ZZ, 4, 6}, {12, 14}}, - {{TQEType::XX, 7, 4}, {7, 5}}, {{TQEType::XY, 7, 4}, {3, 7}}, - {{TQEType::XZ, 7, 4}, {3, 6}}, {{TQEType::YX, 7, 4}, {7, 0}}, - {{TQEType::YY, 7, 4}, {11, 8}}, {{TQEType::YZ, 7, 4}, {11, 12}}, - {{TQEType::ZX, 7, 4}, {7, 1}}, {{TQEType::ZY, 7, 4}, {15, 11}}, - {{TQEType::ZZ, 7, 4}, {15, 14}}, {{TQEType::XX, 6, 4}, {6, 5}}, - {{TQEType::XY, 6, 4}, {2, 7}}, {{TQEType::XZ, 6, 4}, {2, 6}}, - {{TQEType::YX, 6, 4}, {6, 1}}, {{TQEType::YY, 6, 4}, {10, 11}}, - {{TQEType::YZ, 6, 4}, {10, 14}}, {{TQEType::ZX, 6, 4}, {6, 0}}, - {{TQEType::ZY, 6, 4}, {14, 8}}, {{TQEType::ZZ, 6, 4}, {14, 12}}, - {{TQEType::XX, 5, 5}, {5, 5}}, {{TQEType::XY, 5, 5}, {0, 5}}, - {{TQEType::XZ, 5, 5}, {0, 5}}, {{TQEType::YX, 5, 5}, {5, 0}}, - {{TQEType::YY, 5, 5}, {10, 10}}, {{TQEType::YZ, 5, 5}, {10, 15}}, - {{TQEType::ZX, 5, 5}, {5, 0}}, {{TQEType::ZY, 5, 5}, {15, 10}}, - {{TQEType::ZZ, 5, 5}, {15, 15}}, {{TQEType::XX, 4, 5}, {4, 5}}, - {{TQEType::XY, 4, 5}, {1, 5}}, {{TQEType::XZ, 4, 5}, {1, 5}}, - {{TQEType::YX, 4, 5}, {4, 1}}, {{TQEType::YY, 4, 5}, {11, 9}}, - {{TQEType::YZ, 4, 5}, {11, 13}}, {{TQEType::ZX, 4, 5}, {4, 1}}, - {{TQEType::ZY, 4, 5}, {14, 9}}, {{TQEType::ZZ, 4, 5}, {14, 13}}, - {{TQEType::XX, 7, 7}, {6, 6}}, {{TQEType::XY, 7, 7}, {3, 4}}, - {{TQEType::XZ, 7, 7}, {2, 5}}, {{TQEType::YX, 7, 7}, {4, 3}}, - {{TQEType::YY, 7, 7}, {11, 11}}, {{TQEType::YZ, 7, 7}, {8, 15}}, - {{TQEType::ZX, 7, 7}, {5, 2}}, {{TQEType::ZY, 7, 7}, {15, 8}}, - {{TQEType::ZZ, 7, 7}, {13, 13}}, {{TQEType::XX, 6, 7}, {7, 6}}, - {{TQEType::XY, 6, 7}, {2, 4}}, {{TQEType::XZ, 6, 7}, {3, 5}}, - {{TQEType::YX, 6, 7}, {5, 2}}, {{TQEType::YY, 6, 7}, {10, 8}}, - {{TQEType::YZ, 6, 7}, {9, 13}}, {{TQEType::ZX, 6, 7}, {4, 3}}, - {{TQEType::ZY, 6, 7}, {14, 11}}, {{TQEType::ZZ, 6, 7}, {12, 15}}, - {{TQEType::XX, 7, 6}, {6, 7}}, {{TQEType::XY, 7, 6}, {2, 5}}, - {{TQEType::XZ, 7, 6}, {3, 4}}, {{TQEType::YX, 7, 6}, {4, 2}}, - {{TQEType::YY, 7, 6}, {8, 10}}, {{TQEType::YZ, 7, 6}, {11, 14}}, - {{TQEType::ZX, 7, 6}, {5, 3}}, {{TQEType::ZY, 7, 6}, {13, 9}}, - {{TQEType::ZZ, 7, 6}, {15, 12}}, {{TQEType::XX, 6, 6}, {7, 7}}, - {{TQEType::XY, 6, 6}, {3, 5}}, {{TQEType::XZ, 6, 6}, {2, 4}}, - {{TQEType::YX, 6, 6}, {5, 3}}, {{TQEType::YY, 6, 6}, {9, 9}}, - {{TQEType::YZ, 6, 6}, {10, 12}}, {{TQEType::ZX, 6, 6}, {4, 2}}, - {{TQEType::ZY, 6, 6}, {12, 10}}, {{TQEType::ZZ, 6, 6}, {14, 14}}, - {{TQEType::XX, 5, 4}, {5, 4}}, {{TQEType::XY, 5, 4}, {1, 4}}, - {{TQEType::XZ, 5, 4}, {1, 4}}, {{TQEType::YX, 5, 4}, {5, 1}}, - {{TQEType::YY, 5, 4}, {9, 11}}, {{TQEType::YZ, 5, 4}, {9, 14}}, - {{TQEType::ZX, 5, 4}, {5, 1}}, {{TQEType::ZY, 5, 4}, {13, 11}}, - {{TQEType::ZZ, 5, 4}, {13, 14}}, {{TQEType::XX, 4, 4}, {4, 4}}, - {{TQEType::XY, 4, 4}, {0, 4}}, {{TQEType::XZ, 4, 4}, {0, 4}}, - {{TQEType::YX, 4, 4}, {4, 0}}, {{TQEType::YY, 4, 4}, {8, 8}}, - {{TQEType::YZ, 4, 4}, {8, 12}}, {{TQEType::ZX, 4, 4}, {4, 0}}, - {{TQEType::ZY, 4, 4}, {12, 8}}, {{TQEType::ZZ, 4, 4}, {12, 12}}, - {{TQEType::XX, 13, 5}, {13, 1}}, {{TQEType::XY, 13, 5}, {8, 9}}, - {{TQEType::XZ, 13, 5}, {8, 13}}, {{TQEType::YX, 13, 5}, {13, 4}}, - {{TQEType::YY, 13, 5}, {2, 6}}, {{TQEType::YZ, 13, 5}, {2, 7}}, - {{TQEType::ZX, 13, 5}, {13, 0}}, {{TQEType::ZY, 13, 5}, {7, 10}}, - {{TQEType::ZZ, 13, 5}, {7, 15}}, {{TQEType::XX, 14, 5}, {14, 0}}, - {{TQEType::XY, 14, 5}, {11, 10}}, {{TQEType::XZ, 14, 5}, {11, 15}}, - {{TQEType::YX, 14, 5}, {14, 4}}, {{TQEType::YY, 14, 5}, {1, 6}}, - {{TQEType::YZ, 14, 5}, {1, 7}}, {{TQEType::ZX, 14, 5}, {14, 1}}, - {{TQEType::ZY, 14, 5}, {4, 9}}, {{TQEType::ZZ, 14, 5}, {4, 13}}, - {{TQEType::XX, 15, 7}, {14, 2}}, {{TQEType::XY, 15, 7}, {11, 8}}, - {{TQEType::XZ, 15, 7}, {10, 13}}, {{TQEType::YX, 15, 7}, {12, 7}}, - {{TQEType::YY, 15, 7}, {3, 7}}, {{TQEType::YZ, 15, 7}, {0, 7}}, - {{TQEType::ZX, 15, 7}, {13, 2}}, {{TQEType::ZY, 15, 7}, {7, 8}}, - {{TQEType::ZZ, 15, 7}, {5, 13}}, {{TQEType::XX, 12, 7}, {13, 3}}, - {{TQEType::XY, 12, 7}, {8, 11}}, {{TQEType::XZ, 12, 7}, {9, 15}}, - {{TQEType::YX, 12, 7}, {15, 7}}, {{TQEType::YY, 12, 7}, {0, 7}}, - {{TQEType::YZ, 12, 7}, {3, 7}}, {{TQEType::ZX, 12, 7}, {14, 3}}, - {{TQEType::ZY, 12, 7}, {4, 11}}, {{TQEType::ZZ, 12, 7}, {6, 15}}, - {{TQEType::XX, 15, 6}, {14, 3}}, {{TQEType::XY, 15, 6}, {10, 9}}, - {{TQEType::XZ, 15, 6}, {11, 12}}, {{TQEType::YX, 15, 6}, {12, 6}}, - {{TQEType::YY, 15, 6}, {0, 6}}, {{TQEType::YZ, 15, 6}, {3, 6}}, - {{TQEType::ZX, 15, 6}, {13, 3}}, {{TQEType::ZY, 15, 6}, {5, 9}}, - {{TQEType::ZZ, 15, 6}, {7, 12}}, {{TQEType::XX, 12, 6}, {13, 2}}, - {{TQEType::XY, 12, 6}, {9, 10}}, {{TQEType::XZ, 12, 6}, {8, 14}}, - {{TQEType::YX, 12, 6}, {15, 6}}, {{TQEType::YY, 12, 6}, {3, 6}}, - {{TQEType::YZ, 12, 6}, {0, 6}}, {{TQEType::ZX, 12, 6}, {14, 2}}, - {{TQEType::ZY, 12, 6}, {6, 10}}, {{TQEType::ZZ, 12, 6}, {4, 14}}, - {{TQEType::XX, 13, 4}, {13, 0}}, {{TQEType::XY, 13, 4}, {9, 8}}, - {{TQEType::XZ, 13, 4}, {9, 12}}, {{TQEType::YX, 13, 4}, {13, 5}}, - {{TQEType::YY, 13, 4}, {1, 7}}, {{TQEType::YZ, 13, 4}, {1, 6}}, - {{TQEType::ZX, 13, 4}, {13, 1}}, {{TQEType::ZY, 13, 4}, {5, 11}}, - {{TQEType::ZZ, 13, 4}, {5, 14}}, {{TQEType::XX, 14, 4}, {14, 1}}, - {{TQEType::XY, 14, 4}, {10, 11}}, {{TQEType::XZ, 14, 4}, {10, 14}}, - {{TQEType::YX, 14, 4}, {14, 5}}, {{TQEType::YY, 14, 4}, {2, 7}}, - {{TQEType::YZ, 14, 4}, {2, 6}}, {{TQEType::ZX, 14, 4}, {14, 0}}, - {{TQEType::ZY, 14, 4}, {6, 8}}, {{TQEType::ZZ, 14, 4}, {6, 12}}, - {{TQEType::XX, 15, 5}, {15, 0}}, {{TQEType::XY, 15, 5}, {10, 10}}, - {{TQEType::XZ, 15, 5}, {10, 15}}, {{TQEType::YX, 15, 5}, {15, 5}}, - {{TQEType::YY, 15, 5}, {0, 5}}, {{TQEType::YZ, 15, 5}, {0, 5}}, - {{TQEType::ZX, 15, 5}, {15, 0}}, {{TQEType::ZY, 15, 5}, {5, 10}}, - {{TQEType::ZZ, 15, 5}, {5, 15}}, {{TQEType::XX, 12, 5}, {12, 1}}, - {{TQEType::XY, 12, 5}, {9, 9}}, {{TQEType::XZ, 12, 5}, {9, 13}}, - {{TQEType::YX, 12, 5}, {12, 5}}, {{TQEType::YY, 12, 5}, {3, 5}}, - {{TQEType::YZ, 12, 5}, {3, 5}}, {{TQEType::ZX, 12, 5}, {12, 1}}, - {{TQEType::ZY, 12, 5}, {6, 9}}, {{TQEType::ZZ, 12, 5}, {6, 13}}, - {{TQEType::XX, 13, 7}, {12, 3}}, {{TQEType::XY, 13, 7}, {9, 11}}, - {{TQEType::XZ, 13, 7}, {8, 15}}, {{TQEType::YX, 13, 7}, {14, 6}}, - {{TQEType::YY, 13, 7}, {1, 4}}, {{TQEType::YZ, 13, 7}, {2, 5}}, - {{TQEType::ZX, 13, 7}, {15, 2}}, {{TQEType::ZY, 13, 7}, {5, 8}}, - {{TQEType::ZZ, 13, 7}, {7, 13}}, {{TQEType::XX, 14, 7}, {15, 2}}, - {{TQEType::XY, 14, 7}, {10, 8}}, {{TQEType::XZ, 14, 7}, {11, 13}}, - {{TQEType::YX, 14, 7}, {13, 6}}, {{TQEType::YY, 14, 7}, {2, 4}}, - {{TQEType::YZ, 14, 7}, {1, 5}}, {{TQEType::ZX, 14, 7}, {12, 3}}, - {{TQEType::ZY, 14, 7}, {6, 11}}, {{TQEType::ZZ, 14, 7}, {4, 15}}, - {{TQEType::XX, 13, 6}, {12, 2}}, {{TQEType::XY, 13, 6}, {8, 10}}, - {{TQEType::XZ, 13, 6}, {9, 14}}, {{TQEType::YX, 13, 6}, {14, 7}}, - {{TQEType::YY, 13, 6}, {2, 5}}, {{TQEType::YZ, 13, 6}, {1, 4}}, - {{TQEType::ZX, 13, 6}, {15, 3}}, {{TQEType::ZY, 13, 6}, {7, 9}}, - {{TQEType::ZZ, 13, 6}, {5, 12}}, {{TQEType::XX, 14, 6}, {15, 3}}, - {{TQEType::XY, 14, 6}, {11, 9}}, {{TQEType::XZ, 14, 6}, {10, 12}}, - {{TQEType::YX, 14, 6}, {13, 7}}, {{TQEType::YY, 14, 6}, {1, 5}}, - {{TQEType::YZ, 14, 6}, {2, 4}}, {{TQEType::ZX, 14, 6}, {12, 2}}, - {{TQEType::ZY, 14, 6}, {4, 10}}, {{TQEType::ZZ, 14, 6}, {6, 14}}, - {{TQEType::XX, 15, 4}, {15, 1}}, {{TQEType::XY, 15, 4}, {11, 11}}, - {{TQEType::XZ, 15, 4}, {11, 14}}, {{TQEType::YX, 15, 4}, {15, 4}}, - {{TQEType::YY, 15, 4}, {3, 4}}, {{TQEType::YZ, 15, 4}, {3, 4}}, - {{TQEType::ZX, 15, 4}, {15, 1}}, {{TQEType::ZY, 15, 4}, {7, 11}}, - {{TQEType::ZZ, 15, 4}, {7, 14}}, {{TQEType::XX, 12, 4}, {12, 0}}, - {{TQEType::XY, 12, 4}, {8, 8}}, {{TQEType::XZ, 12, 4}, {8, 12}}, - {{TQEType::YX, 12, 4}, {12, 4}}, {{TQEType::YY, 12, 4}, {0, 4}}, - {{TQEType::YZ, 12, 4}, {0, 4}}, {{TQEType::ZX, 12, 4}, {12, 0}}, - {{TQEType::ZY, 12, 4}, {4, 8}}, {{TQEType::ZZ, 12, 4}, {4, 12}}, - {{TQEType::XX, 9, 5}, {9, 1}}, {{TQEType::XY, 9, 5}, {12, 9}}, - {{TQEType::XZ, 9, 5}, {12, 13}}, {{TQEType::YX, 9, 5}, {9, 0}}, - {{TQEType::YY, 9, 5}, {6, 10}}, {{TQEType::YZ, 9, 5}, {6, 15}}, - {{TQEType::ZX, 9, 5}, {9, 4}}, {{TQEType::ZY, 9, 5}, {3, 6}}, - {{TQEType::ZZ, 9, 5}, {3, 7}}, {{TQEType::XX, 11, 5}, {11, 0}}, - {{TQEType::XY, 11, 5}, {14, 10}}, {{TQEType::XZ, 11, 5}, {14, 15}}, - {{TQEType::YX, 11, 5}, {11, 1}}, {{TQEType::YY, 11, 5}, {4, 9}}, - {{TQEType::YZ, 11, 5}, {4, 13}}, {{TQEType::ZX, 11, 5}, {11, 4}}, - {{TQEType::ZY, 11, 5}, {1, 6}}, {{TQEType::ZZ, 11, 5}, {1, 7}}, - {{TQEType::XX, 10, 7}, {11, 2}}, {{TQEType::XY, 10, 7}, {14, 8}}, - {{TQEType::XZ, 10, 7}, {15, 13}}, {{TQEType::YX, 10, 7}, {9, 2}}, - {{TQEType::YY, 10, 7}, {6, 8}}, {{TQEType::YZ, 10, 7}, {5, 13}}, - {{TQEType::ZX, 10, 7}, {8, 7}}, {{TQEType::ZY, 10, 7}, {2, 7}}, - {{TQEType::ZZ, 10, 7}, {0, 7}}, {{TQEType::XX, 8, 7}, {9, 3}}, - {{TQEType::XY, 8, 7}, {12, 11}}, {{TQEType::XZ, 8, 7}, {13, 15}}, - {{TQEType::YX, 8, 7}, {11, 3}}, {{TQEType::YY, 8, 7}, {4, 11}}, - {{TQEType::YZ, 8, 7}, {7, 15}}, {{TQEType::ZX, 8, 7}, {10, 7}}, - {{TQEType::ZY, 8, 7}, {0, 7}}, {{TQEType::ZZ, 8, 7}, {2, 7}}, - {{TQEType::XX, 10, 6}, {11, 3}}, {{TQEType::XY, 10, 6}, {15, 9}}, - {{TQEType::XZ, 10, 6}, {14, 12}}, {{TQEType::YX, 10, 6}, {9, 3}}, - {{TQEType::YY, 10, 6}, {5, 9}}, {{TQEType::YZ, 10, 6}, {6, 12}}, - {{TQEType::ZX, 10, 6}, {8, 6}}, {{TQEType::ZY, 10, 6}, {0, 6}}, - {{TQEType::ZZ, 10, 6}, {2, 6}}, {{TQEType::XX, 8, 6}, {9, 2}}, - {{TQEType::XY, 8, 6}, {13, 10}}, {{TQEType::XZ, 8, 6}, {12, 14}}, - {{TQEType::YX, 8, 6}, {11, 2}}, {{TQEType::YY, 8, 6}, {7, 10}}, - {{TQEType::YZ, 8, 6}, {4, 14}}, {{TQEType::ZX, 8, 6}, {10, 6}}, - {{TQEType::ZY, 8, 6}, {2, 6}}, {{TQEType::ZZ, 8, 6}, {0, 6}}, - {{TQEType::XX, 9, 4}, {9, 0}}, {{TQEType::XY, 9, 4}, {13, 8}}, - {{TQEType::XZ, 9, 4}, {13, 12}}, {{TQEType::YX, 9, 4}, {9, 1}}, - {{TQEType::YY, 9, 4}, {5, 11}}, {{TQEType::YZ, 9, 4}, {5, 14}}, - {{TQEType::ZX, 9, 4}, {9, 5}}, {{TQEType::ZY, 9, 4}, {1, 7}}, - {{TQEType::ZZ, 9, 4}, {1, 6}}, {{TQEType::XX, 11, 4}, {11, 1}}, - {{TQEType::XY, 11, 4}, {15, 11}}, {{TQEType::XZ, 11, 4}, {15, 14}}, - {{TQEType::YX, 11, 4}, {11, 0}}, {{TQEType::YY, 11, 4}, {7, 8}}, - {{TQEType::YZ, 11, 4}, {7, 12}}, {{TQEType::ZX, 11, 4}, {11, 5}}, - {{TQEType::ZY, 11, 4}, {3, 7}}, {{TQEType::ZZ, 11, 4}, {3, 6}}, - {{TQEType::XX, 10, 5}, {10, 0}}, {{TQEType::XY, 10, 5}, {15, 10}}, - {{TQEType::XZ, 10, 5}, {15, 15}}, {{TQEType::YX, 10, 5}, {10, 0}}, - {{TQEType::YY, 10, 5}, {5, 10}}, {{TQEType::YZ, 10, 5}, {5, 15}}, - {{TQEType::ZX, 10, 5}, {10, 5}}, {{TQEType::ZY, 10, 5}, {0, 5}}, - {{TQEType::ZZ, 10, 5}, {0, 5}}, {{TQEType::XX, 8, 5}, {8, 1}}, - {{TQEType::XY, 8, 5}, {13, 9}}, {{TQEType::XZ, 8, 5}, {13, 13}}, - {{TQEType::YX, 8, 5}, {8, 1}}, {{TQEType::YY, 8, 5}, {7, 9}}, - {{TQEType::YZ, 8, 5}, {7, 13}}, {{TQEType::ZX, 8, 5}, {8, 5}}, - {{TQEType::ZY, 8, 5}, {2, 5}}, {{TQEType::ZZ, 8, 5}, {2, 5}}, - {{TQEType::XX, 9, 7}, {8, 3}}, {{TQEType::XY, 9, 7}, {13, 11}}, - {{TQEType::XZ, 9, 7}, {12, 15}}, {{TQEType::YX, 9, 7}, {10, 2}}, - {{TQEType::YY, 9, 7}, {5, 8}}, {{TQEType::YZ, 9, 7}, {6, 13}}, - {{TQEType::ZX, 9, 7}, {11, 6}}, {{TQEType::ZY, 9, 7}, {1, 4}}, - {{TQEType::ZZ, 9, 7}, {3, 5}}, {{TQEType::XX, 11, 7}, {10, 2}}, - {{TQEType::XY, 11, 7}, {15, 8}}, {{TQEType::XZ, 11, 7}, {14, 13}}, - {{TQEType::YX, 11, 7}, {8, 3}}, {{TQEType::YY, 11, 7}, {7, 11}}, - {{TQEType::YZ, 11, 7}, {4, 15}}, {{TQEType::ZX, 11, 7}, {9, 6}}, - {{TQEType::ZY, 11, 7}, {3, 4}}, {{TQEType::ZZ, 11, 7}, {1, 5}}, - {{TQEType::XX, 9, 6}, {8, 2}}, {{TQEType::XY, 9, 6}, {12, 10}}, - {{TQEType::XZ, 9, 6}, {13, 14}}, {{TQEType::YX, 9, 6}, {10, 3}}, - {{TQEType::YY, 9, 6}, {6, 9}}, {{TQEType::YZ, 9, 6}, {5, 12}}, - {{TQEType::ZX, 9, 6}, {11, 7}}, {{TQEType::ZY, 9, 6}, {3, 5}}, - {{TQEType::ZZ, 9, 6}, {1, 4}}, {{TQEType::XX, 11, 6}, {10, 3}}, - {{TQEType::XY, 11, 6}, {14, 9}}, {{TQEType::XZ, 11, 6}, {15, 12}}, - {{TQEType::YX, 11, 6}, {8, 2}}, {{TQEType::YY, 11, 6}, {4, 10}}, - {{TQEType::YZ, 11, 6}, {7, 14}}, {{TQEType::ZX, 11, 6}, {9, 7}}, - {{TQEType::ZY, 11, 6}, {1, 5}}, {{TQEType::ZZ, 11, 6}, {3, 4}}, - {{TQEType::XX, 10, 4}, {10, 1}}, {{TQEType::XY, 10, 4}, {14, 11}}, - {{TQEType::XZ, 10, 4}, {14, 14}}, {{TQEType::YX, 10, 4}, {10, 1}}, - {{TQEType::YY, 10, 4}, {6, 11}}, {{TQEType::YZ, 10, 4}, {6, 14}}, - {{TQEType::ZX, 10, 4}, {10, 4}}, {{TQEType::ZY, 10, 4}, {2, 4}}, - {{TQEType::ZZ, 10, 4}, {2, 4}}, {{TQEType::XX, 8, 4}, {8, 0}}, - {{TQEType::XY, 8, 4}, {12, 8}}, {{TQEType::XZ, 8, 4}, {12, 12}}, - {{TQEType::YX, 8, 4}, {8, 0}}, {{TQEType::YY, 8, 4}, {4, 8}}, - {{TQEType::YZ, 8, 4}, {4, 12}}, {{TQEType::ZX, 8, 4}, {8, 4}}, - {{TQEType::ZY, 8, 4}, {0, 4}}, {{TQEType::ZZ, 8, 4}, {0, 4}}, - {{TQEType::XX, 1, 7}, {0, 7}}, {{TQEType::XY, 1, 7}, {5, 7}}, - {{TQEType::XZ, 1, 7}, {4, 7}}, {{TQEType::YX, 1, 7}, {2, 6}}, - {{TQEType::YY, 1, 7}, {13, 4}}, {{TQEType::YZ, 1, 7}, {14, 5}}, - {{TQEType::ZX, 1, 7}, {3, 6}}, {{TQEType::ZY, 1, 7}, {9, 4}}, - {{TQEType::ZZ, 1, 7}, {11, 5}}, {{TQEType::XX, 3, 7}, {2, 6}}, - {{TQEType::XY, 3, 7}, {7, 4}}, {{TQEType::XZ, 3, 7}, {6, 5}}, - {{TQEType::YX, 3, 7}, {0, 7}}, {{TQEType::YY, 3, 7}, {15, 7}}, - {{TQEType::YZ, 3, 7}, {12, 7}}, {{TQEType::ZX, 3, 7}, {1, 6}}, - {{TQEType::ZY, 3, 7}, {11, 4}}, {{TQEType::ZZ, 3, 7}, {9, 5}}, - {{TQEType::XX, 2, 7}, {3, 6}}, {{TQEType::XY, 2, 7}, {6, 4}}, - {{TQEType::XZ, 2, 7}, {7, 5}}, {{TQEType::YX, 2, 7}, {1, 6}}, - {{TQEType::YY, 2, 7}, {14, 4}}, {{TQEType::YZ, 2, 7}, {13, 5}}, - {{TQEType::ZX, 2, 7}, {0, 7}}, {{TQEType::ZY, 2, 7}, {10, 7}}, - {{TQEType::ZZ, 2, 7}, {8, 7}}, {{TQEType::XX, 0, 7}, {1, 7}}, - {{TQEType::XY, 0, 7}, {4, 7}}, {{TQEType::XZ, 0, 7}, {5, 7}}, - {{TQEType::YX, 0, 7}, {3, 7}}, {{TQEType::YY, 0, 7}, {12, 7}}, - {{TQEType::YZ, 0, 7}, {15, 7}}, {{TQEType::ZX, 0, 7}, {2, 7}}, - {{TQEType::ZY, 0, 7}, {8, 7}}, {{TQEType::ZZ, 0, 7}, {10, 7}}, - {{TQEType::XX, 1, 6}, {0, 6}}, {{TQEType::XY, 1, 6}, {4, 6}}, - {{TQEType::XZ, 1, 6}, {5, 6}}, {{TQEType::YX, 1, 6}, {2, 7}}, - {{TQEType::YY, 1, 6}, {14, 5}}, {{TQEType::YZ, 1, 6}, {13, 4}}, - {{TQEType::ZX, 1, 6}, {3, 7}}, {{TQEType::ZY, 1, 6}, {11, 5}}, - {{TQEType::ZZ, 1, 6}, {9, 4}}, {{TQEType::XX, 3, 6}, {2, 7}}, - {{TQEType::XY, 3, 6}, {6, 5}}, {{TQEType::XZ, 3, 6}, {7, 4}}, - {{TQEType::YX, 3, 6}, {0, 6}}, {{TQEType::YY, 3, 6}, {12, 6}}, - {{TQEType::YZ, 3, 6}, {15, 6}}, {{TQEType::ZX, 3, 6}, {1, 7}}, - {{TQEType::ZY, 3, 6}, {9, 5}}, {{TQEType::ZZ, 3, 6}, {11, 4}}, - {{TQEType::XX, 2, 6}, {3, 7}}, {{TQEType::XY, 2, 6}, {7, 5}}, - {{TQEType::XZ, 2, 6}, {6, 4}}, {{TQEType::YX, 2, 6}, {1, 7}}, - {{TQEType::YY, 2, 6}, {13, 5}}, {{TQEType::YZ, 2, 6}, {14, 4}}, - {{TQEType::ZX, 2, 6}, {0, 6}}, {{TQEType::ZY, 2, 6}, {8, 6}}, - {{TQEType::ZZ, 2, 6}, {10, 6}}, {{TQEType::XX, 0, 6}, {1, 6}}, - {{TQEType::XY, 0, 6}, {5, 6}}, {{TQEType::XZ, 0, 6}, {4, 6}}, - {{TQEType::YX, 0, 6}, {3, 6}}, {{TQEType::YY, 0, 6}, {15, 6}}, - {{TQEType::YZ, 0, 6}, {12, 6}}, {{TQEType::ZX, 0, 6}, {2, 6}}, - {{TQEType::ZY, 0, 6}, {10, 6}}, {{TQEType::ZZ, 0, 6}, {8, 6}}, - {{TQEType::XX, 1, 5}, {1, 5}}, {{TQEType::XY, 1, 5}, {4, 5}}, - {{TQEType::XZ, 1, 5}, {4, 5}}, {{TQEType::YX, 1, 5}, {1, 4}}, - {{TQEType::YY, 1, 5}, {14, 6}}, {{TQEType::YZ, 1, 5}, {14, 7}}, - {{TQEType::ZX, 1, 5}, {1, 4}}, {{TQEType::ZY, 1, 5}, {11, 6}}, - {{TQEType::ZZ, 1, 5}, {11, 7}}, {{TQEType::XX, 3, 5}, {3, 4}}, - {{TQEType::XY, 3, 5}, {6, 6}}, {{TQEType::XZ, 3, 5}, {6, 7}}, - {{TQEType::YX, 3, 5}, {3, 5}}, {{TQEType::YY, 3, 5}, {12, 5}}, - {{TQEType::YZ, 3, 5}, {12, 5}}, {{TQEType::ZX, 3, 5}, {3, 4}}, - {{TQEType::ZY, 3, 5}, {9, 6}}, {{TQEType::ZZ, 3, 5}, {9, 7}}, - {{TQEType::XX, 2, 5}, {2, 4}}, {{TQEType::XY, 2, 5}, {7, 6}}, - {{TQEType::XZ, 2, 5}, {7, 7}}, {{TQEType::YX, 2, 5}, {2, 4}}, - {{TQEType::YY, 2, 5}, {13, 6}}, {{TQEType::YZ, 2, 5}, {13, 7}}, - {{TQEType::ZX, 2, 5}, {2, 5}}, {{TQEType::ZY, 2, 5}, {8, 5}}, - {{TQEType::ZZ, 2, 5}, {8, 5}}, {{TQEType::XX, 0, 5}, {0, 5}}, - {{TQEType::XY, 0, 5}, {5, 5}}, {{TQEType::XZ, 0, 5}, {5, 5}}, - {{TQEType::YX, 0, 5}, {0, 5}}, {{TQEType::YY, 0, 5}, {15, 5}}, - {{TQEType::YZ, 0, 5}, {15, 5}}, {{TQEType::ZX, 0, 5}, {0, 5}}, - {{TQEType::ZY, 0, 5}, {10, 5}}, {{TQEType::ZZ, 0, 5}, {10, 5}}, - {{TQEType::XX, 1, 4}, {1, 4}}, {{TQEType::XY, 1, 4}, {5, 4}}, - {{TQEType::XZ, 1, 4}, {5, 4}}, {{TQEType::YX, 1, 4}, {1, 5}}, - {{TQEType::YY, 1, 4}, {13, 7}}, {{TQEType::YZ, 1, 4}, {13, 6}}, - {{TQEType::ZX, 1, 4}, {1, 5}}, {{TQEType::ZY, 1, 4}, {9, 7}}, - {{TQEType::ZZ, 1, 4}, {9, 6}}, {{TQEType::XX, 3, 4}, {3, 5}}, - {{TQEType::XY, 3, 4}, {7, 7}}, {{TQEType::XZ, 3, 4}, {7, 6}}, - {{TQEType::YX, 3, 4}, {3, 4}}, {{TQEType::YY, 3, 4}, {15, 4}}, - {{TQEType::YZ, 3, 4}, {15, 4}}, {{TQEType::ZX, 3, 4}, {3, 5}}, - {{TQEType::ZY, 3, 4}, {11, 7}}, {{TQEType::ZZ, 3, 4}, {11, 6}}, - {{TQEType::XX, 2, 4}, {2, 5}}, {{TQEType::XY, 2, 4}, {6, 7}}, - {{TQEType::XZ, 2, 4}, {6, 6}}, {{TQEType::YX, 2, 4}, {2, 5}}, - {{TQEType::YY, 2, 4}, {14, 7}}, {{TQEType::YZ, 2, 4}, {14, 6}}, - {{TQEType::ZX, 2, 4}, {2, 4}}, {{TQEType::ZY, 2, 4}, {10, 4}}, - {{TQEType::ZZ, 2, 4}, {10, 4}}, {{TQEType::XX, 0, 4}, {0, 4}}, - {{TQEType::XY, 0, 4}, {4, 4}}, {{TQEType::XZ, 0, 4}, {4, 4}}, - {{TQEType::YX, 0, 4}, {0, 4}}, {{TQEType::YY, 0, 4}, {12, 4}}, - {{TQEType::YZ, 0, 4}, {12, 4}}, {{TQEType::ZX, 0, 4}, {0, 4}}, - {{TQEType::ZY, 0, 4}, {8, 4}}, {{TQEType::ZZ, 0, 4}, {8, 4}}, - {{TQEType::XX, 5, 13}, {1, 13}}, {{TQEType::XY, 5, 13}, {4, 13}}, - {{TQEType::XZ, 5, 13}, {0, 13}}, {{TQEType::YX, 5, 13}, {9, 8}}, - {{TQEType::YY, 5, 13}, {6, 2}}, {{TQEType::YZ, 5, 13}, {10, 7}}, - {{TQEType::ZX, 5, 13}, {13, 8}}, {{TQEType::ZY, 5, 13}, {7, 2}}, - {{TQEType::ZZ, 5, 13}, {15, 7}}, {{TQEType::XX, 4, 13}, {0, 13}}, - {{TQEType::XY, 4, 13}, {5, 13}}, {{TQEType::XZ, 4, 13}, {1, 13}}, - {{TQEType::YX, 4, 13}, {8, 9}}, {{TQEType::YY, 4, 13}, {7, 1}}, - {{TQEType::YZ, 4, 13}, {11, 5}}, {{TQEType::ZX, 4, 13}, {12, 9}}, - {{TQEType::ZY, 4, 13}, {6, 1}}, {{TQEType::ZZ, 4, 13}, {14, 5}}, - {{TQEType::XX, 7, 15}, {2, 14}}, {{TQEType::XY, 7, 15}, {7, 12}}, - {{TQEType::XZ, 7, 15}, {2, 13}}, {{TQEType::YX, 7, 15}, {8, 11}}, - {{TQEType::YY, 7, 15}, {7, 3}}, {{TQEType::YZ, 7, 15}, {8, 7}}, - {{TQEType::ZX, 7, 15}, {13, 10}}, {{TQEType::ZY, 7, 15}, {7, 0}}, - {{TQEType::ZZ, 7, 15}, {13, 5}}, {{TQEType::XX, 6, 15}, {3, 14}}, - {{TQEType::XY, 6, 15}, {6, 12}}, {{TQEType::XZ, 6, 15}, {3, 13}}, - {{TQEType::YX, 6, 15}, {9, 10}}, {{TQEType::YY, 6, 15}, {6, 0}}, - {{TQEType::YZ, 6, 15}, {9, 5}}, {{TQEType::ZX, 6, 15}, {12, 11}}, - {{TQEType::ZY, 6, 15}, {6, 3}}, {{TQEType::ZZ, 6, 15}, {12, 7}}, - {{TQEType::XX, 5, 14}, {0, 14}}, {{TQEType::XY, 5, 14}, {4, 14}}, - {{TQEType::XZ, 5, 14}, {1, 14}}, {{TQEType::YX, 5, 14}, {10, 11}}, - {{TQEType::YY, 5, 14}, {6, 1}}, {{TQEType::YZ, 5, 14}, {9, 4}}, - {{TQEType::ZX, 5, 14}, {15, 11}}, {{TQEType::ZY, 5, 14}, {7, 1}}, - {{TQEType::ZZ, 5, 14}, {13, 4}}, {{TQEType::XX, 4, 14}, {1, 14}}, - {{TQEType::XY, 4, 14}, {5, 14}}, {{TQEType::XZ, 4, 14}, {0, 14}}, - {{TQEType::YX, 4, 14}, {11, 10}}, {{TQEType::YY, 4, 14}, {7, 2}}, - {{TQEType::YZ, 4, 14}, {8, 6}}, {{TQEType::ZX, 4, 14}, {14, 10}}, - {{TQEType::ZY, 4, 14}, {6, 2}}, {{TQEType::ZZ, 4, 14}, {12, 6}}, - {{TQEType::XX, 7, 12}, {3, 13}}, {{TQEType::XY, 7, 12}, {7, 15}}, - {{TQEType::XZ, 7, 12}, {3, 14}}, {{TQEType::YX, 7, 12}, {11, 8}}, - {{TQEType::YY, 7, 12}, {7, 0}}, {{TQEType::YZ, 7, 12}, {11, 4}}, - {{TQEType::ZX, 7, 12}, {15, 9}}, {{TQEType::ZY, 7, 12}, {7, 3}}, - {{TQEType::ZZ, 7, 12}, {15, 6}}, {{TQEType::XX, 6, 12}, {2, 13}}, - {{TQEType::XY, 6, 12}, {6, 15}}, {{TQEType::XZ, 6, 12}, {2, 14}}, - {{TQEType::YX, 6, 12}, {10, 9}}, {{TQEType::YY, 6, 12}, {6, 3}}, - {{TQEType::YZ, 6, 12}, {10, 6}}, {{TQEType::ZX, 6, 12}, {14, 8}}, - {{TQEType::ZY, 6, 12}, {6, 0}}, {{TQEType::ZZ, 6, 12}, {14, 4}}, - {{TQEType::XX, 7, 13}, {3, 12}}, {{TQEType::XY, 7, 13}, {6, 14}}, - {{TQEType::XZ, 7, 13}, {2, 15}}, {{TQEType::YX, 7, 13}, {11, 9}}, - {{TQEType::YY, 7, 13}, {4, 1}}, {{TQEType::YZ, 7, 13}, {8, 5}}, - {{TQEType::ZX, 7, 13}, {15, 8}}, {{TQEType::ZY, 7, 13}, {5, 2}}, - {{TQEType::ZZ, 7, 13}, {13, 7}}, {{TQEType::XX, 6, 13}, {2, 12}}, - {{TQEType::XY, 6, 13}, {7, 14}}, {{TQEType::XZ, 6, 13}, {3, 15}}, - {{TQEType::YX, 6, 13}, {10, 8}}, {{TQEType::YY, 6, 13}, {5, 2}}, - {{TQEType::YZ, 6, 13}, {9, 7}}, {{TQEType::ZX, 6, 13}, {14, 9}}, - {{TQEType::ZY, 6, 13}, {4, 1}}, {{TQEType::ZZ, 6, 13}, {12, 5}}, - {{TQEType::XX, 5, 15}, {0, 15}}, {{TQEType::XY, 5, 15}, {5, 15}}, - {{TQEType::XZ, 5, 15}, {0, 15}}, {{TQEType::YX, 5, 15}, {10, 10}}, - {{TQEType::YY, 5, 15}, {5, 0}}, {{TQEType::YZ, 5, 15}, {10, 5}}, - {{TQEType::ZX, 5, 15}, {15, 10}}, {{TQEType::ZY, 5, 15}, {5, 0}}, - {{TQEType::ZZ, 5, 15}, {15, 5}}, {{TQEType::XX, 4, 15}, {1, 15}}, - {{TQEType::XY, 4, 15}, {4, 15}}, {{TQEType::XZ, 4, 15}, {1, 15}}, - {{TQEType::YX, 4, 15}, {11, 11}}, {{TQEType::YY, 4, 15}, {4, 3}}, - {{TQEType::YZ, 4, 15}, {11, 7}}, {{TQEType::ZX, 4, 15}, {14, 11}}, - {{TQEType::ZY, 4, 15}, {4, 3}}, {{TQEType::ZZ, 4, 15}, {14, 7}}, - {{TQEType::XX, 7, 14}, {2, 15}}, {{TQEType::XY, 7, 14}, {6, 13}}, - {{TQEType::XZ, 7, 14}, {3, 12}}, {{TQEType::YX, 7, 14}, {8, 10}}, - {{TQEType::YY, 7, 14}, {4, 2}}, {{TQEType::YZ, 7, 14}, {11, 6}}, - {{TQEType::ZX, 7, 14}, {13, 11}}, {{TQEType::ZY, 7, 14}, {5, 1}}, - {{TQEType::ZZ, 7, 14}, {15, 4}}, {{TQEType::XX, 6, 14}, {3, 15}}, - {{TQEType::XY, 6, 14}, {7, 13}}, {{TQEType::XZ, 6, 14}, {2, 12}}, - {{TQEType::YX, 6, 14}, {9, 11}}, {{TQEType::YY, 6, 14}, {5, 1}}, - {{TQEType::YZ, 6, 14}, {10, 4}}, {{TQEType::ZX, 6, 14}, {12, 10}}, - {{TQEType::ZY, 6, 14}, {4, 2}}, {{TQEType::ZZ, 6, 14}, {14, 6}}, - {{TQEType::XX, 5, 12}, {1, 12}}, {{TQEType::XY, 5, 12}, {5, 12}}, - {{TQEType::XZ, 5, 12}, {1, 12}}, {{TQEType::YX, 5, 12}, {9, 9}}, - {{TQEType::YY, 5, 12}, {5, 3}}, {{TQEType::YZ, 5, 12}, {9, 6}}, - {{TQEType::ZX, 5, 12}, {13, 9}}, {{TQEType::ZY, 5, 12}, {5, 3}}, - {{TQEType::ZZ, 5, 12}, {13, 6}}, {{TQEType::XX, 4, 12}, {0, 12}}, - {{TQEType::XY, 4, 12}, {4, 12}}, {{TQEType::XZ, 4, 12}, {0, 12}}, - {{TQEType::YX, 4, 12}, {8, 8}}, {{TQEType::YY, 4, 12}, {4, 0}}, - {{TQEType::YZ, 4, 12}, {8, 4}}, {{TQEType::ZX, 4, 12}, {12, 8}}, - {{TQEType::ZY, 4, 12}, {4, 0}}, {{TQEType::ZZ, 4, 12}, {12, 4}}, - {{TQEType::XX, 15, 13}, {11, 8}}, {{TQEType::XY, 15, 13}, {14, 2}}, - {{TQEType::XZ, 15, 13}, {10, 7}}, {{TQEType::YX, 15, 13}, {3, 13}}, - {{TQEType::YY, 15, 13}, {12, 13}}, {{TQEType::YZ, 15, 13}, {0, 13}}, - {{TQEType::ZX, 15, 13}, {7, 8}}, {{TQEType::ZY, 15, 13}, {13, 2}}, - {{TQEType::ZZ, 15, 13}, {5, 7}}, {{TQEType::XX, 12, 13}, {8, 9}}, - {{TQEType::XY, 12, 13}, {13, 1}}, {{TQEType::XZ, 12, 13}, {9, 5}}, - {{TQEType::YX, 12, 13}, {0, 13}}, {{TQEType::YY, 12, 13}, {15, 13}}, - {{TQEType::YZ, 12, 13}, {3, 13}}, {{TQEType::ZX, 12, 13}, {4, 9}}, - {{TQEType::ZY, 12, 13}, {14, 1}}, {{TQEType::ZZ, 12, 13}, {6, 5}}, - {{TQEType::XX, 13, 15}, {8, 11}}, {{TQEType::XY, 13, 15}, {13, 3}}, - {{TQEType::XZ, 13, 15}, {8, 7}}, {{TQEType::YX, 13, 15}, {2, 14}}, - {{TQEType::YY, 13, 15}, {13, 12}}, {{TQEType::YZ, 13, 15}, {2, 13}}, - {{TQEType::ZX, 13, 15}, {7, 10}}, {{TQEType::ZY, 13, 15}, {13, 0}}, - {{TQEType::ZZ, 13, 15}, {7, 5}}, {{TQEType::XX, 14, 15}, {11, 10}}, - {{TQEType::XY, 14, 15}, {14, 0}}, {{TQEType::XZ, 14, 15}, {11, 5}}, - {{TQEType::YX, 14, 15}, {1, 14}}, {{TQEType::YY, 14, 15}, {14, 12}}, - {{TQEType::YZ, 14, 15}, {1, 13}}, {{TQEType::ZX, 14, 15}, {4, 11}}, - {{TQEType::ZY, 14, 15}, {14, 3}}, {{TQEType::ZZ, 14, 15}, {4, 7}}, - {{TQEType::XX, 15, 14}, {10, 11}}, {{TQEType::XY, 15, 14}, {14, 1}}, - {{TQEType::XZ, 15, 14}, {11, 4}}, {{TQEType::YX, 15, 14}, {0, 14}}, - {{TQEType::YY, 15, 14}, {12, 14}}, {{TQEType::YZ, 15, 14}, {3, 14}}, - {{TQEType::ZX, 15, 14}, {5, 11}}, {{TQEType::ZY, 15, 14}, {13, 1}}, - {{TQEType::ZZ, 15, 14}, {7, 4}}, {{TQEType::XX, 12, 14}, {9, 10}}, - {{TQEType::XY, 12, 14}, {13, 2}}, {{TQEType::XZ, 12, 14}, {8, 6}}, - {{TQEType::YX, 12, 14}, {3, 14}}, {{TQEType::YY, 12, 14}, {15, 14}}, - {{TQEType::YZ, 12, 14}, {0, 14}}, {{TQEType::ZX, 12, 14}, {6, 10}}, - {{TQEType::ZY, 12, 14}, {14, 2}}, {{TQEType::ZZ, 12, 14}, {4, 6}}, - {{TQEType::XX, 13, 12}, {9, 8}}, {{TQEType::XY, 13, 12}, {13, 0}}, - {{TQEType::XZ, 13, 12}, {9, 4}}, {{TQEType::YX, 13, 12}, {1, 13}}, - {{TQEType::YY, 13, 12}, {13, 15}}, {{TQEType::YZ, 13, 12}, {1, 14}}, - {{TQEType::ZX, 13, 12}, {5, 9}}, {{TQEType::ZY, 13, 12}, {13, 3}}, - {{TQEType::ZZ, 13, 12}, {5, 6}}, {{TQEType::XX, 14, 12}, {10, 9}}, - {{TQEType::XY, 14, 12}, {14, 3}}, {{TQEType::XZ, 14, 12}, {10, 6}}, - {{TQEType::YX, 14, 12}, {2, 13}}, {{TQEType::YY, 14, 12}, {14, 15}}, - {{TQEType::YZ, 14, 12}, {2, 14}}, {{TQEType::ZX, 14, 12}, {6, 8}}, - {{TQEType::ZY, 14, 12}, {14, 0}}, {{TQEType::ZZ, 14, 12}, {6, 4}}, - {{TQEType::XX, 13, 13}, {9, 9}}, {{TQEType::XY, 13, 13}, {12, 1}}, - {{TQEType::XZ, 13, 13}, {8, 5}}, {{TQEType::YX, 13, 13}, {1, 12}}, - {{TQEType::YY, 13, 13}, {14, 14}}, {{TQEType::YZ, 13, 13}, {2, 15}}, - {{TQEType::ZX, 13, 13}, {5, 8}}, {{TQEType::ZY, 13, 13}, {15, 2}}, - {{TQEType::ZZ, 13, 13}, {7, 7}}, {{TQEType::XX, 14, 13}, {10, 8}}, - {{TQEType::XY, 14, 13}, {15, 2}}, {{TQEType::XZ, 14, 13}, {11, 7}}, - {{TQEType::YX, 14, 13}, {2, 12}}, {{TQEType::YY, 14, 13}, {13, 14}}, - {{TQEType::YZ, 14, 13}, {1, 15}}, {{TQEType::ZX, 14, 13}, {6, 9}}, - {{TQEType::ZY, 14, 13}, {12, 1}}, {{TQEType::ZZ, 14, 13}, {4, 5}}, - {{TQEType::XX, 15, 15}, {10, 10}}, {{TQEType::XY, 15, 15}, {15, 0}}, - {{TQEType::XZ, 15, 15}, {10, 5}}, {{TQEType::YX, 15, 15}, {0, 15}}, - {{TQEType::YY, 15, 15}, {15, 15}}, {{TQEType::YZ, 15, 15}, {0, 15}}, - {{TQEType::ZX, 15, 15}, {5, 10}}, {{TQEType::ZY, 15, 15}, {15, 0}}, - {{TQEType::ZZ, 15, 15}, {5, 5}}, {{TQEType::XX, 12, 15}, {9, 11}}, - {{TQEType::XY, 12, 15}, {12, 3}}, {{TQEType::XZ, 12, 15}, {9, 7}}, - {{TQEType::YX, 12, 15}, {3, 15}}, {{TQEType::YY, 12, 15}, {12, 15}}, - {{TQEType::YZ, 12, 15}, {3, 15}}, {{TQEType::ZX, 12, 15}, {6, 11}}, - {{TQEType::ZY, 12, 15}, {12, 3}}, {{TQEType::ZZ, 12, 15}, {6, 7}}, - {{TQEType::XX, 13, 14}, {8, 10}}, {{TQEType::XY, 13, 14}, {12, 2}}, - {{TQEType::XZ, 13, 14}, {9, 6}}, {{TQEType::YX, 13, 14}, {2, 15}}, - {{TQEType::YY, 13, 14}, {14, 13}}, {{TQEType::YZ, 13, 14}, {1, 12}}, - {{TQEType::ZX, 13, 14}, {7, 11}}, {{TQEType::ZY, 13, 14}, {15, 1}}, - {{TQEType::ZZ, 13, 14}, {5, 4}}, {{TQEType::XX, 14, 14}, {11, 11}}, - {{TQEType::XY, 14, 14}, {15, 1}}, {{TQEType::XZ, 14, 14}, {10, 4}}, - {{TQEType::YX, 14, 14}, {1, 15}}, {{TQEType::YY, 14, 14}, {13, 13}}, - {{TQEType::YZ, 14, 14}, {2, 12}}, {{TQEType::ZX, 14, 14}, {4, 10}}, - {{TQEType::ZY, 14, 14}, {12, 2}}, {{TQEType::ZZ, 14, 14}, {6, 6}}, - {{TQEType::XX, 15, 12}, {11, 9}}, {{TQEType::XY, 15, 12}, {15, 3}}, - {{TQEType::XZ, 15, 12}, {11, 6}}, {{TQEType::YX, 15, 12}, {3, 12}}, - {{TQEType::YY, 15, 12}, {15, 12}}, {{TQEType::YZ, 15, 12}, {3, 12}}, - {{TQEType::ZX, 15, 12}, {7, 9}}, {{TQEType::ZY, 15, 12}, {15, 3}}, - {{TQEType::ZZ, 15, 12}, {7, 6}}, {{TQEType::XX, 12, 12}, {8, 8}}, - {{TQEType::XY, 12, 12}, {12, 0}}, {{TQEType::XZ, 12, 12}, {8, 4}}, - {{TQEType::YX, 12, 12}, {0, 12}}, {{TQEType::YY, 12, 12}, {12, 12}}, - {{TQEType::YZ, 12, 12}, {0, 12}}, {{TQEType::ZX, 12, 12}, {4, 8}}, - {{TQEType::ZY, 12, 12}, {12, 0}}, {{TQEType::ZZ, 12, 12}, {4, 4}}, - {{TQEType::XX, 10, 13}, {14, 8}}, {{TQEType::XY, 10, 13}, {11, 2}}, - {{TQEType::XZ, 10, 13}, {15, 7}}, {{TQEType::YX, 10, 13}, {6, 8}}, - {{TQEType::YY, 10, 13}, {9, 2}}, {{TQEType::YZ, 10, 13}, {5, 7}}, - {{TQEType::ZX, 10, 13}, {2, 13}}, {{TQEType::ZY, 10, 13}, {8, 13}}, - {{TQEType::ZZ, 10, 13}, {0, 13}}, {{TQEType::XX, 8, 13}, {12, 9}}, - {{TQEType::XY, 8, 13}, {9, 1}}, {{TQEType::XZ, 8, 13}, {13, 5}}, - {{TQEType::YX, 8, 13}, {4, 9}}, {{TQEType::YY, 8, 13}, {11, 1}}, - {{TQEType::YZ, 8, 13}, {7, 5}}, {{TQEType::ZX, 8, 13}, {0, 13}}, - {{TQEType::ZY, 8, 13}, {10, 13}}, {{TQEType::ZZ, 8, 13}, {2, 13}}, - {{TQEType::XX, 9, 15}, {12, 11}}, {{TQEType::XY, 9, 15}, {9, 3}}, - {{TQEType::XZ, 9, 15}, {12, 7}}, {{TQEType::YX, 9, 15}, {6, 10}}, - {{TQEType::YY, 9, 15}, {9, 0}}, {{TQEType::YZ, 9, 15}, {6, 5}}, - {{TQEType::ZX, 9, 15}, {3, 14}}, {{TQEType::ZY, 9, 15}, {9, 12}}, - {{TQEType::ZZ, 9, 15}, {3, 13}}, {{TQEType::XX, 11, 15}, {14, 10}}, - {{TQEType::XY, 11, 15}, {11, 0}}, {{TQEType::XZ, 11, 15}, {14, 5}}, - {{TQEType::YX, 11, 15}, {4, 11}}, {{TQEType::YY, 11, 15}, {11, 3}}, - {{TQEType::YZ, 11, 15}, {4, 7}}, {{TQEType::ZX, 11, 15}, {1, 14}}, - {{TQEType::ZY, 11, 15}, {11, 12}}, {{TQEType::ZZ, 11, 15}, {1, 13}}, - {{TQEType::XX, 10, 14}, {15, 11}}, {{TQEType::XY, 10, 14}, {11, 1}}, - {{TQEType::XZ, 10, 14}, {14, 4}}, {{TQEType::YX, 10, 14}, {5, 11}}, - {{TQEType::YY, 10, 14}, {9, 1}}, {{TQEType::YZ, 10, 14}, {6, 4}}, - {{TQEType::ZX, 10, 14}, {0, 14}}, {{TQEType::ZY, 10, 14}, {8, 14}}, - {{TQEType::ZZ, 10, 14}, {2, 14}}, {{TQEType::XX, 8, 14}, {13, 10}}, - {{TQEType::XY, 8, 14}, {9, 2}}, {{TQEType::XZ, 8, 14}, {12, 6}}, - {{TQEType::YX, 8, 14}, {7, 10}}, {{TQEType::YY, 8, 14}, {11, 2}}, - {{TQEType::YZ, 8, 14}, {4, 6}}, {{TQEType::ZX, 8, 14}, {2, 14}}, - {{TQEType::ZY, 8, 14}, {10, 14}}, {{TQEType::ZZ, 8, 14}, {0, 14}}, - {{TQEType::XX, 9, 12}, {13, 8}}, {{TQEType::XY, 9, 12}, {9, 0}}, - {{TQEType::XZ, 9, 12}, {13, 4}}, {{TQEType::YX, 9, 12}, {5, 9}}, - {{TQEType::YY, 9, 12}, {9, 3}}, {{TQEType::YZ, 9, 12}, {5, 6}}, - {{TQEType::ZX, 9, 12}, {1, 13}}, {{TQEType::ZY, 9, 12}, {9, 15}}, - {{TQEType::ZZ, 9, 12}, {1, 14}}, {{TQEType::XX, 11, 12}, {15, 9}}, - {{TQEType::XY, 11, 12}, {11, 3}}, {{TQEType::XZ, 11, 12}, {15, 6}}, - {{TQEType::YX, 11, 12}, {7, 8}}, {{TQEType::YY, 11, 12}, {11, 0}}, - {{TQEType::YZ, 11, 12}, {7, 4}}, {{TQEType::ZX, 11, 12}, {3, 13}}, - {{TQEType::ZY, 11, 12}, {11, 15}}, {{TQEType::ZZ, 11, 12}, {3, 14}}, - {{TQEType::XX, 9, 13}, {13, 9}}, {{TQEType::XY, 9, 13}, {8, 1}}, - {{TQEType::XZ, 9, 13}, {12, 5}}, {{TQEType::YX, 9, 13}, {5, 8}}, - {{TQEType::YY, 9, 13}, {10, 2}}, {{TQEType::YZ, 9, 13}, {6, 7}}, - {{TQEType::ZX, 9, 13}, {1, 12}}, {{TQEType::ZY, 9, 13}, {11, 14}}, - {{TQEType::ZZ, 9, 13}, {3, 15}}, {{TQEType::XX, 11, 13}, {15, 8}}, - {{TQEType::XY, 11, 13}, {10, 2}}, {{TQEType::XZ, 11, 13}, {14, 7}}, - {{TQEType::YX, 11, 13}, {7, 9}}, {{TQEType::YY, 11, 13}, {8, 1}}, - {{TQEType::YZ, 11, 13}, {4, 5}}, {{TQEType::ZX, 11, 13}, {3, 12}}, - {{TQEType::ZY, 11, 13}, {9, 14}}, {{TQEType::ZZ, 11, 13}, {1, 15}}, - {{TQEType::XX, 10, 15}, {15, 10}}, {{TQEType::XY, 10, 15}, {10, 0}}, - {{TQEType::XZ, 10, 15}, {15, 5}}, {{TQEType::YX, 10, 15}, {5, 10}}, - {{TQEType::YY, 10, 15}, {10, 0}}, {{TQEType::YZ, 10, 15}, {5, 5}}, - {{TQEType::ZX, 10, 15}, {0, 15}}, {{TQEType::ZY, 10, 15}, {10, 15}}, - {{TQEType::ZZ, 10, 15}, {0, 15}}, {{TQEType::XX, 8, 15}, {13, 11}}, - {{TQEType::XY, 8, 15}, {8, 3}}, {{TQEType::XZ, 8, 15}, {13, 7}}, - {{TQEType::YX, 8, 15}, {7, 11}}, {{TQEType::YY, 8, 15}, {8, 3}}, - {{TQEType::YZ, 8, 15}, {7, 7}}, {{TQEType::ZX, 8, 15}, {2, 15}}, - {{TQEType::ZY, 8, 15}, {8, 15}}, {{TQEType::ZZ, 8, 15}, {2, 15}}, - {{TQEType::XX, 9, 14}, {12, 10}}, {{TQEType::XY, 9, 14}, {8, 2}}, - {{TQEType::XZ, 9, 14}, {13, 6}}, {{TQEType::YX, 9, 14}, {6, 11}}, - {{TQEType::YY, 9, 14}, {10, 1}}, {{TQEType::YZ, 9, 14}, {5, 4}}, - {{TQEType::ZX, 9, 14}, {3, 15}}, {{TQEType::ZY, 9, 14}, {11, 13}}, - {{TQEType::ZZ, 9, 14}, {1, 12}}, {{TQEType::XX, 11, 14}, {14, 11}}, - {{TQEType::XY, 11, 14}, {10, 1}}, {{TQEType::XZ, 11, 14}, {15, 4}}, - {{TQEType::YX, 11, 14}, {4, 10}}, {{TQEType::YY, 11, 14}, {8, 2}}, - {{TQEType::YZ, 11, 14}, {7, 6}}, {{TQEType::ZX, 11, 14}, {1, 15}}, - {{TQEType::ZY, 11, 14}, {9, 13}}, {{TQEType::ZZ, 11, 14}, {3, 12}}, - {{TQEType::XX, 10, 12}, {14, 9}}, {{TQEType::XY, 10, 12}, {10, 3}}, - {{TQEType::XZ, 10, 12}, {14, 6}}, {{TQEType::YX, 10, 12}, {6, 9}}, - {{TQEType::YY, 10, 12}, {10, 3}}, {{TQEType::YZ, 10, 12}, {6, 6}}, - {{TQEType::ZX, 10, 12}, {2, 12}}, {{TQEType::ZY, 10, 12}, {10, 12}}, - {{TQEType::ZZ, 10, 12}, {2, 12}}, {{TQEType::XX, 8, 12}, {12, 8}}, - {{TQEType::XY, 8, 12}, {8, 0}}, {{TQEType::XZ, 8, 12}, {12, 4}}, - {{TQEType::YX, 8, 12}, {4, 8}}, {{TQEType::YY, 8, 12}, {8, 0}}, - {{TQEType::YZ, 8, 12}, {4, 4}}, {{TQEType::ZX, 8, 12}, {0, 12}}, - {{TQEType::ZY, 8, 12}, {8, 12}}, {{TQEType::ZZ, 8, 12}, {0, 12}}, - {{TQEType::XX, 1, 13}, {5, 13}}, {{TQEType::XY, 1, 13}, {0, 13}}, - {{TQEType::XZ, 1, 13}, {4, 13}}, {{TQEType::YX, 1, 13}, {13, 12}}, - {{TQEType::YY, 1, 13}, {2, 14}}, {{TQEType::YZ, 1, 13}, {14, 15}}, - {{TQEType::ZX, 1, 13}, {9, 12}}, {{TQEType::ZY, 1, 13}, {3, 14}}, - {{TQEType::ZZ, 1, 13}, {11, 15}}, {{TQEType::XX, 3, 13}, {7, 12}}, - {{TQEType::XY, 3, 13}, {2, 14}}, {{TQEType::XZ, 3, 13}, {6, 15}}, - {{TQEType::YX, 3, 13}, {15, 13}}, {{TQEType::YY, 3, 13}, {0, 13}}, - {{TQEType::YZ, 3, 13}, {12, 13}}, {{TQEType::ZX, 3, 13}, {11, 12}}, - {{TQEType::ZY, 3, 13}, {1, 14}}, {{TQEType::ZZ, 3, 13}, {9, 15}}, - {{TQEType::XX, 2, 13}, {6, 12}}, {{TQEType::XY, 2, 13}, {3, 14}}, - {{TQEType::XZ, 2, 13}, {7, 15}}, {{TQEType::YX, 2, 13}, {14, 12}}, - {{TQEType::YY, 2, 13}, {1, 14}}, {{TQEType::YZ, 2, 13}, {13, 15}}, - {{TQEType::ZX, 2, 13}, {10, 13}}, {{TQEType::ZY, 2, 13}, {0, 13}}, - {{TQEType::ZZ, 2, 13}, {8, 13}}, {{TQEType::XX, 0, 13}, {4, 13}}, - {{TQEType::XY, 0, 13}, {1, 13}}, {{TQEType::XZ, 0, 13}, {5, 13}}, - {{TQEType::YX, 0, 13}, {12, 13}}, {{TQEType::YY, 0, 13}, {3, 13}}, - {{TQEType::YZ, 0, 13}, {15, 13}}, {{TQEType::ZX, 0, 13}, {8, 13}}, - {{TQEType::ZY, 0, 13}, {2, 13}}, {{TQEType::ZZ, 0, 13}, {10, 13}}, - {{TQEType::XX, 1, 14}, {4, 14}}, {{TQEType::XY, 1, 14}, {0, 14}}, - {{TQEType::XZ, 1, 14}, {5, 14}}, {{TQEType::YX, 1, 14}, {14, 15}}, - {{TQEType::YY, 1, 14}, {2, 13}}, {{TQEType::YZ, 1, 14}, {13, 12}}, - {{TQEType::ZX, 1, 14}, {11, 15}}, {{TQEType::ZY, 1, 14}, {3, 13}}, - {{TQEType::ZZ, 1, 14}, {9, 12}}, {{TQEType::XX, 3, 14}, {6, 15}}, - {{TQEType::XY, 3, 14}, {2, 13}}, {{TQEType::XZ, 3, 14}, {7, 12}}, - {{TQEType::YX, 3, 14}, {12, 14}}, {{TQEType::YY, 3, 14}, {0, 14}}, - {{TQEType::YZ, 3, 14}, {15, 14}}, {{TQEType::ZX, 3, 14}, {9, 15}}, - {{TQEType::ZY, 3, 14}, {1, 13}}, {{TQEType::ZZ, 3, 14}, {11, 12}}, - {{TQEType::XX, 2, 14}, {7, 15}}, {{TQEType::XY, 2, 14}, {3, 13}}, - {{TQEType::XZ, 2, 14}, {6, 12}}, {{TQEType::YX, 2, 14}, {13, 15}}, - {{TQEType::YY, 2, 14}, {1, 13}}, {{TQEType::YZ, 2, 14}, {14, 12}}, - {{TQEType::ZX, 2, 14}, {8, 14}}, {{TQEType::ZY, 2, 14}, {0, 14}}, - {{TQEType::ZZ, 2, 14}, {10, 14}}, {{TQEType::XX, 0, 14}, {5, 14}}, - {{TQEType::XY, 0, 14}, {1, 14}}, {{TQEType::XZ, 0, 14}, {4, 14}}, - {{TQEType::YX, 0, 14}, {15, 14}}, {{TQEType::YY, 0, 14}, {3, 14}}, - {{TQEType::YZ, 0, 14}, {12, 14}}, {{TQEType::ZX, 0, 14}, {10, 14}}, - {{TQEType::ZY, 0, 14}, {2, 14}}, {{TQEType::ZZ, 0, 14}, {8, 14}}, - {{TQEType::XX, 1, 15}, {4, 15}}, {{TQEType::XY, 1, 15}, {1, 15}}, - {{TQEType::XZ, 1, 15}, {4, 15}}, {{TQEType::YX, 1, 15}, {14, 14}}, - {{TQEType::YY, 1, 15}, {1, 12}}, {{TQEType::YZ, 1, 15}, {14, 13}}, - {{TQEType::ZX, 1, 15}, {11, 14}}, {{TQEType::ZY, 1, 15}, {1, 12}}, - {{TQEType::ZZ, 1, 15}, {11, 13}}, {{TQEType::XX, 3, 15}, {6, 14}}, - {{TQEType::XY, 3, 15}, {3, 12}}, {{TQEType::XZ, 3, 15}, {6, 13}}, - {{TQEType::YX, 3, 15}, {12, 15}}, {{TQEType::YY, 3, 15}, {3, 15}}, - {{TQEType::YZ, 3, 15}, {12, 15}}, {{TQEType::ZX, 3, 15}, {9, 14}}, - {{TQEType::ZY, 3, 15}, {3, 12}}, {{TQEType::ZZ, 3, 15}, {9, 13}}, - {{TQEType::XX, 2, 15}, {7, 14}}, {{TQEType::XY, 2, 15}, {2, 12}}, - {{TQEType::XZ, 2, 15}, {7, 13}}, {{TQEType::YX, 2, 15}, {13, 14}}, - {{TQEType::YY, 2, 15}, {2, 12}}, {{TQEType::YZ, 2, 15}, {13, 13}}, - {{TQEType::ZX, 2, 15}, {8, 15}}, {{TQEType::ZY, 2, 15}, {2, 15}}, - {{TQEType::ZZ, 2, 15}, {8, 15}}, {{TQEType::XX, 0, 15}, {5, 15}}, - {{TQEType::XY, 0, 15}, {0, 15}}, {{TQEType::XZ, 0, 15}, {5, 15}}, - {{TQEType::YX, 0, 15}, {15, 15}}, {{TQEType::YY, 0, 15}, {0, 15}}, - {{TQEType::YZ, 0, 15}, {15, 15}}, {{TQEType::ZX, 0, 15}, {10, 15}}, - {{TQEType::ZY, 0, 15}, {0, 15}}, {{TQEType::ZZ, 0, 15}, {10, 15}}, - {{TQEType::XX, 1, 12}, {5, 12}}, {{TQEType::XY, 1, 12}, {1, 12}}, - {{TQEType::XZ, 1, 12}, {5, 12}}, {{TQEType::YX, 1, 12}, {13, 13}}, - {{TQEType::YY, 1, 12}, {1, 15}}, {{TQEType::YZ, 1, 12}, {13, 14}}, - {{TQEType::ZX, 1, 12}, {9, 13}}, {{TQEType::ZY, 1, 12}, {1, 15}}, - {{TQEType::ZZ, 1, 12}, {9, 14}}, {{TQEType::XX, 3, 12}, {7, 13}}, - {{TQEType::XY, 3, 12}, {3, 15}}, {{TQEType::XZ, 3, 12}, {7, 14}}, - {{TQEType::YX, 3, 12}, {15, 12}}, {{TQEType::YY, 3, 12}, {3, 12}}, - {{TQEType::YZ, 3, 12}, {15, 12}}, {{TQEType::ZX, 3, 12}, {11, 13}}, - {{TQEType::ZY, 3, 12}, {3, 15}}, {{TQEType::ZZ, 3, 12}, {11, 14}}, - {{TQEType::XX, 2, 12}, {6, 13}}, {{TQEType::XY, 2, 12}, {2, 15}}, - {{TQEType::XZ, 2, 12}, {6, 14}}, {{TQEType::YX, 2, 12}, {14, 13}}, - {{TQEType::YY, 2, 12}, {2, 15}}, {{TQEType::YZ, 2, 12}, {14, 14}}, - {{TQEType::ZX, 2, 12}, {10, 12}}, {{TQEType::ZY, 2, 12}, {2, 12}}, - {{TQEType::ZZ, 2, 12}, {10, 12}}, {{TQEType::XX, 0, 12}, {4, 12}}, - {{TQEType::XY, 0, 12}, {0, 12}}, {{TQEType::XZ, 0, 12}, {4, 12}}, - {{TQEType::YX, 0, 12}, {12, 12}}, {{TQEType::YY, 0, 12}, {0, 12}}, - {{TQEType::YZ, 0, 12}, {12, 12}}, {{TQEType::ZX, 0, 12}, {8, 12}}, - {{TQEType::ZY, 0, 12}, {0, 12}}, {{TQEType::ZZ, 0, 12}, {8, 12}}, - {{TQEType::XX, 5, 9}, {1, 9}}, {{TQEType::XY, 5, 9}, {0, 9}}, - {{TQEType::XZ, 5, 9}, {4, 9}}, {{TQEType::YX, 5, 9}, {9, 12}}, - {{TQEType::YY, 5, 9}, {10, 6}}, {{TQEType::YZ, 5, 9}, {6, 3}}, - {{TQEType::ZX, 5, 9}, {13, 12}}, {{TQEType::ZY, 5, 9}, {15, 6}}, - {{TQEType::ZZ, 5, 9}, {7, 3}}, {{TQEType::XX, 4, 9}, {0, 9}}, - {{TQEType::XY, 4, 9}, {1, 9}}, {{TQEType::XZ, 4, 9}, {5, 9}}, - {{TQEType::YX, 4, 9}, {8, 13}}, {{TQEType::YY, 4, 9}, {11, 5}}, - {{TQEType::YZ, 4, 9}, {7, 1}}, {{TQEType::ZX, 4, 9}, {12, 13}}, - {{TQEType::ZY, 4, 9}, {14, 5}}, {{TQEType::ZZ, 4, 9}, {6, 1}}, - {{TQEType::XX, 5, 11}, {0, 11}}, {{TQEType::XY, 5, 11}, {1, 11}}, - {{TQEType::XZ, 5, 11}, {4, 11}}, {{TQEType::YX, 5, 11}, {10, 14}}, - {{TQEType::YY, 5, 11}, {9, 4}}, {{TQEType::YZ, 5, 11}, {6, 1}}, - {{TQEType::ZX, 5, 11}, {15, 14}}, {{TQEType::ZY, 5, 11}, {13, 4}}, - {{TQEType::ZZ, 5, 11}, {7, 1}}, {{TQEType::XX, 4, 11}, {1, 11}}, - {{TQEType::XY, 4, 11}, {0, 11}}, {{TQEType::XZ, 4, 11}, {5, 11}}, - {{TQEType::YX, 4, 11}, {11, 15}}, {{TQEType::YY, 4, 11}, {8, 7}}, - {{TQEType::YZ, 4, 11}, {7, 3}}, {{TQEType::ZX, 4, 11}, {14, 15}}, - {{TQEType::ZY, 4, 11}, {12, 7}}, {{TQEType::ZZ, 4, 11}, {6, 3}}, - {{TQEType::XX, 7, 10}, {2, 11}}, {{TQEType::XY, 7, 10}, {2, 9}}, - {{TQEType::XZ, 7, 10}, {7, 8}}, {{TQEType::YX, 7, 10}, {8, 14}}, - {{TQEType::YY, 7, 10}, {8, 6}}, {{TQEType::YZ, 7, 10}, {7, 2}}, - {{TQEType::ZX, 7, 10}, {13, 15}}, {{TQEType::ZY, 7, 10}, {13, 5}}, - {{TQEType::ZZ, 7, 10}, {7, 0}}, {{TQEType::XX, 6, 10}, {3, 11}}, - {{TQEType::XY, 6, 10}, {3, 9}}, {{TQEType::XZ, 6, 10}, {6, 8}}, - {{TQEType::YX, 6, 10}, {9, 15}}, {{TQEType::YY, 6, 10}, {9, 5}}, - {{TQEType::YZ, 6, 10}, {6, 0}}, {{TQEType::ZX, 6, 10}, {12, 14}}, - {{TQEType::ZY, 6, 10}, {12, 6}}, {{TQEType::ZZ, 6, 10}, {6, 2}}, - {{TQEType::XX, 7, 8}, {3, 9}}, {{TQEType::XY, 7, 8}, {3, 11}}, - {{TQEType::XZ, 7, 8}, {7, 10}}, {{TQEType::YX, 7, 8}, {11, 12}}, - {{TQEType::YY, 7, 8}, {11, 4}}, {{TQEType::YZ, 7, 8}, {7, 0}}, - {{TQEType::ZX, 7, 8}, {15, 13}}, {{TQEType::ZY, 7, 8}, {15, 7}}, - {{TQEType::ZZ, 7, 8}, {7, 2}}, {{TQEType::XX, 6, 8}, {2, 9}}, - {{TQEType::XY, 6, 8}, {2, 11}}, {{TQEType::XZ, 6, 8}, {6, 10}}, - {{TQEType::YX, 6, 8}, {10, 13}}, {{TQEType::YY, 6, 8}, {10, 7}}, - {{TQEType::YZ, 6, 8}, {6, 2}}, {{TQEType::ZX, 6, 8}, {14, 12}}, - {{TQEType::ZY, 6, 8}, {14, 4}}, {{TQEType::ZZ, 6, 8}, {6, 0}}, - {{TQEType::XX, 7, 9}, {3, 8}}, {{TQEType::XY, 7, 9}, {2, 10}}, - {{TQEType::XZ, 7, 9}, {6, 11}}, {{TQEType::YX, 7, 9}, {11, 13}}, - {{TQEType::YY, 7, 9}, {8, 5}}, {{TQEType::YZ, 7, 9}, {4, 1}}, - {{TQEType::ZX, 7, 9}, {15, 12}}, {{TQEType::ZY, 7, 9}, {13, 6}}, - {{TQEType::ZZ, 7, 9}, {5, 3}}, {{TQEType::XX, 6, 9}, {2, 8}}, - {{TQEType::XY, 6, 9}, {3, 10}}, {{TQEType::XZ, 6, 9}, {7, 11}}, - {{TQEType::YX, 6, 9}, {10, 12}}, {{TQEType::YY, 6, 9}, {9, 6}}, - {{TQEType::YZ, 6, 9}, {5, 3}}, {{TQEType::ZX, 6, 9}, {14, 13}}, - {{TQEType::ZY, 6, 9}, {12, 5}}, {{TQEType::ZZ, 6, 9}, {4, 1}}, - {{TQEType::XX, 7, 11}, {2, 10}}, {{TQEType::XY, 7, 11}, {3, 8}}, - {{TQEType::XZ, 7, 11}, {6, 9}}, {{TQEType::YX, 7, 11}, {8, 15}}, - {{TQEType::YY, 7, 11}, {11, 7}}, {{TQEType::YZ, 7, 11}, {4, 3}}, - {{TQEType::ZX, 7, 11}, {13, 14}}, {{TQEType::ZY, 7, 11}, {15, 4}}, - {{TQEType::ZZ, 7, 11}, {5, 1}}, {{TQEType::XX, 6, 11}, {3, 10}}, - {{TQEType::XY, 6, 11}, {2, 8}}, {{TQEType::XZ, 6, 11}, {7, 9}}, - {{TQEType::YX, 6, 11}, {9, 14}}, {{TQEType::YY, 6, 11}, {10, 4}}, - {{TQEType::YZ, 6, 11}, {5, 1}}, {{TQEType::ZX, 6, 11}, {12, 15}}, - {{TQEType::ZY, 6, 11}, {14, 7}}, {{TQEType::ZZ, 6, 11}, {4, 3}}, - {{TQEType::XX, 5, 10}, {0, 10}}, {{TQEType::XY, 5, 10}, {0, 10}}, - {{TQEType::XZ, 5, 10}, {5, 10}}, {{TQEType::YX, 5, 10}, {10, 15}}, - {{TQEType::YY, 5, 10}, {10, 5}}, {{TQEType::YZ, 5, 10}, {5, 0}}, - {{TQEType::ZX, 5, 10}, {15, 15}}, {{TQEType::ZY, 5, 10}, {15, 5}}, - {{TQEType::ZZ, 5, 10}, {5, 0}}, {{TQEType::XX, 4, 10}, {1, 10}}, - {{TQEType::XY, 4, 10}, {1, 10}}, {{TQEType::XZ, 4, 10}, {4, 10}}, - {{TQEType::YX, 4, 10}, {11, 14}}, {{TQEType::YY, 4, 10}, {11, 6}}, - {{TQEType::YZ, 4, 10}, {4, 2}}, {{TQEType::ZX, 4, 10}, {14, 14}}, - {{TQEType::ZY, 4, 10}, {14, 6}}, {{TQEType::ZZ, 4, 10}, {4, 2}}, - {{TQEType::XX, 5, 8}, {1, 8}}, {{TQEType::XY, 5, 8}, {1, 8}}, - {{TQEType::XZ, 5, 8}, {5, 8}}, {{TQEType::YX, 5, 8}, {9, 13}}, - {{TQEType::YY, 5, 8}, {9, 7}}, {{TQEType::YZ, 5, 8}, {5, 2}}, - {{TQEType::ZX, 5, 8}, {13, 13}}, {{TQEType::ZY, 5, 8}, {13, 7}}, - {{TQEType::ZZ, 5, 8}, {5, 2}}, {{TQEType::XX, 4, 8}, {0, 8}}, - {{TQEType::XY, 4, 8}, {0, 8}}, {{TQEType::XZ, 4, 8}, {4, 8}}, - {{TQEType::YX, 4, 8}, {8, 12}}, {{TQEType::YY, 4, 8}, {8, 4}}, - {{TQEType::YZ, 4, 8}, {4, 0}}, {{TQEType::ZX, 4, 8}, {12, 12}}, - {{TQEType::ZY, 4, 8}, {12, 4}}, {{TQEType::ZZ, 4, 8}, {4, 0}}, - {{TQEType::XX, 15, 9}, {11, 12}}, {{TQEType::XY, 15, 9}, {10, 6}}, - {{TQEType::XZ, 15, 9}, {14, 3}}, {{TQEType::YX, 15, 9}, {3, 9}}, - {{TQEType::YY, 15, 9}, {0, 9}}, {{TQEType::YZ, 15, 9}, {12, 9}}, - {{TQEType::ZX, 15, 9}, {7, 12}}, {{TQEType::ZY, 15, 9}, {5, 6}}, - {{TQEType::ZZ, 15, 9}, {13, 3}}, {{TQEType::XX, 12, 9}, {8, 13}}, - {{TQEType::XY, 12, 9}, {9, 5}}, {{TQEType::XZ, 12, 9}, {13, 1}}, - {{TQEType::YX, 12, 9}, {0, 9}}, {{TQEType::YY, 12, 9}, {3, 9}}, - {{TQEType::YZ, 12, 9}, {15, 9}}, {{TQEType::ZX, 12, 9}, {4, 13}}, - {{TQEType::ZY, 12, 9}, {6, 5}}, {{TQEType::ZZ, 12, 9}, {14, 1}}, - {{TQEType::XX, 15, 11}, {10, 14}}, {{TQEType::XY, 15, 11}, {11, 4}}, - {{TQEType::XZ, 15, 11}, {14, 1}}, {{TQEType::YX, 15, 11}, {0, 11}}, - {{TQEType::YY, 15, 11}, {3, 11}}, {{TQEType::YZ, 15, 11}, {12, 11}}, - {{TQEType::ZX, 15, 11}, {5, 14}}, {{TQEType::ZY, 15, 11}, {7, 4}}, - {{TQEType::ZZ, 15, 11}, {13, 1}}, {{TQEType::XX, 12, 11}, {9, 15}}, - {{TQEType::XY, 12, 11}, {8, 7}}, {{TQEType::XZ, 12, 11}, {13, 3}}, - {{TQEType::YX, 12, 11}, {3, 11}}, {{TQEType::YY, 12, 11}, {0, 11}}, - {{TQEType::YZ, 12, 11}, {15, 11}}, {{TQEType::ZX, 12, 11}, {6, 15}}, - {{TQEType::ZY, 12, 11}, {4, 7}}, {{TQEType::ZZ, 12, 11}, {14, 3}}, - {{TQEType::XX, 13, 10}, {8, 14}}, {{TQEType::XY, 13, 10}, {8, 6}}, - {{TQEType::XZ, 13, 10}, {13, 2}}, {{TQEType::YX, 13, 10}, {2, 11}}, - {{TQEType::YY, 13, 10}, {2, 9}}, {{TQEType::YZ, 13, 10}, {13, 8}}, - {{TQEType::ZX, 13, 10}, {7, 15}}, {{TQEType::ZY, 13, 10}, {7, 5}}, - {{TQEType::ZZ, 13, 10}, {13, 0}}, {{TQEType::XX, 14, 10}, {11, 15}}, - {{TQEType::XY, 14, 10}, {11, 5}}, {{TQEType::XZ, 14, 10}, {14, 0}}, - {{TQEType::YX, 14, 10}, {1, 11}}, {{TQEType::YY, 14, 10}, {1, 9}}, - {{TQEType::YZ, 14, 10}, {14, 8}}, {{TQEType::ZX, 14, 10}, {4, 14}}, - {{TQEType::ZY, 14, 10}, {4, 6}}, {{TQEType::ZZ, 14, 10}, {14, 2}}, - {{TQEType::XX, 13, 8}, {9, 12}}, {{TQEType::XY, 13, 8}, {9, 4}}, - {{TQEType::XZ, 13, 8}, {13, 0}}, {{TQEType::YX, 13, 8}, {1, 9}}, - {{TQEType::YY, 13, 8}, {1, 11}}, {{TQEType::YZ, 13, 8}, {13, 10}}, - {{TQEType::ZX, 13, 8}, {5, 13}}, {{TQEType::ZY, 13, 8}, {5, 7}}, - {{TQEType::ZZ, 13, 8}, {13, 2}}, {{TQEType::XX, 14, 8}, {10, 13}}, - {{TQEType::XY, 14, 8}, {10, 7}}, {{TQEType::XZ, 14, 8}, {14, 2}}, - {{TQEType::YX, 14, 8}, {2, 9}}, {{TQEType::YY, 14, 8}, {2, 11}}, - {{TQEType::YZ, 14, 8}, {14, 10}}, {{TQEType::ZX, 14, 8}, {6, 12}}, - {{TQEType::ZY, 14, 8}, {6, 4}}, {{TQEType::ZZ, 14, 8}, {14, 0}}, - {{TQEType::XX, 13, 9}, {9, 13}}, {{TQEType::XY, 13, 9}, {8, 5}}, - {{TQEType::XZ, 13, 9}, {12, 1}}, {{TQEType::YX, 13, 9}, {1, 8}}, - {{TQEType::YY, 13, 9}, {2, 10}}, {{TQEType::YZ, 13, 9}, {14, 11}}, - {{TQEType::ZX, 13, 9}, {5, 12}}, {{TQEType::ZY, 13, 9}, {7, 6}}, - {{TQEType::ZZ, 13, 9}, {15, 3}}, {{TQEType::XX, 14, 9}, {10, 12}}, - {{TQEType::XY, 14, 9}, {11, 6}}, {{TQEType::XZ, 14, 9}, {15, 3}}, - {{TQEType::YX, 14, 9}, {2, 8}}, {{TQEType::YY, 14, 9}, {1, 10}}, - {{TQEType::YZ, 14, 9}, {13, 11}}, {{TQEType::ZX, 14, 9}, {6, 13}}, - {{TQEType::ZY, 14, 9}, {4, 5}}, {{TQEType::ZZ, 14, 9}, {12, 1}}, - {{TQEType::XX, 13, 11}, {8, 15}}, {{TQEType::XY, 13, 11}, {9, 7}}, - {{TQEType::XZ, 13, 11}, {12, 3}}, {{TQEType::YX, 13, 11}, {2, 10}}, - {{TQEType::YY, 13, 11}, {1, 8}}, {{TQEType::YZ, 13, 11}, {14, 9}}, - {{TQEType::ZX, 13, 11}, {7, 14}}, {{TQEType::ZY, 13, 11}, {5, 4}}, - {{TQEType::ZZ, 13, 11}, {15, 1}}, {{TQEType::XX, 14, 11}, {11, 14}}, - {{TQEType::XY, 14, 11}, {10, 4}}, {{TQEType::XZ, 14, 11}, {15, 1}}, - {{TQEType::YX, 14, 11}, {1, 10}}, {{TQEType::YY, 14, 11}, {2, 8}}, - {{TQEType::YZ, 14, 11}, {13, 9}}, {{TQEType::ZX, 14, 11}, {4, 15}}, - {{TQEType::ZY, 14, 11}, {6, 7}}, {{TQEType::ZZ, 14, 11}, {12, 3}}, - {{TQEType::XX, 15, 10}, {10, 15}}, {{TQEType::XY, 15, 10}, {10, 5}}, - {{TQEType::XZ, 15, 10}, {15, 0}}, {{TQEType::YX, 15, 10}, {0, 10}}, - {{TQEType::YY, 15, 10}, {0, 10}}, {{TQEType::YZ, 15, 10}, {15, 10}}, - {{TQEType::ZX, 15, 10}, {5, 15}}, {{TQEType::ZY, 15, 10}, {5, 5}}, - {{TQEType::ZZ, 15, 10}, {15, 0}}, {{TQEType::XX, 12, 10}, {9, 14}}, - {{TQEType::XY, 12, 10}, {9, 6}}, {{TQEType::XZ, 12, 10}, {12, 2}}, - {{TQEType::YX, 12, 10}, {3, 10}}, {{TQEType::YY, 12, 10}, {3, 10}}, - {{TQEType::YZ, 12, 10}, {12, 10}}, {{TQEType::ZX, 12, 10}, {6, 14}}, - {{TQEType::ZY, 12, 10}, {6, 6}}, {{TQEType::ZZ, 12, 10}, {12, 2}}, - {{TQEType::XX, 15, 8}, {11, 13}}, {{TQEType::XY, 15, 8}, {11, 7}}, - {{TQEType::XZ, 15, 8}, {15, 2}}, {{TQEType::YX, 15, 8}, {3, 8}}, - {{TQEType::YY, 15, 8}, {3, 8}}, {{TQEType::YZ, 15, 8}, {15, 8}}, - {{TQEType::ZX, 15, 8}, {7, 13}}, {{TQEType::ZY, 15, 8}, {7, 7}}, - {{TQEType::ZZ, 15, 8}, {15, 2}}, {{TQEType::XX, 12, 8}, {8, 12}}, - {{TQEType::XY, 12, 8}, {8, 4}}, {{TQEType::XZ, 12, 8}, {12, 0}}, - {{TQEType::YX, 12, 8}, {0, 8}}, {{TQEType::YY, 12, 8}, {0, 8}}, - {{TQEType::YZ, 12, 8}, {12, 8}}, {{TQEType::ZX, 12, 8}, {4, 12}}, - {{TQEType::ZY, 12, 8}, {4, 4}}, {{TQEType::ZZ, 12, 8}, {12, 0}}, - {{TQEType::XX, 10, 9}, {14, 12}}, {{TQEType::XY, 10, 9}, {15, 6}}, - {{TQEType::XZ, 10, 9}, {11, 3}}, {{TQEType::YX, 10, 9}, {6, 12}}, - {{TQEType::YY, 10, 9}, {5, 6}}, {{TQEType::YZ, 10, 9}, {9, 3}}, - {{TQEType::ZX, 10, 9}, {2, 9}}, {{TQEType::ZY, 10, 9}, {0, 9}}, - {{TQEType::ZZ, 10, 9}, {8, 9}}, {{TQEType::XX, 8, 9}, {12, 13}}, - {{TQEType::XY, 8, 9}, {13, 5}}, {{TQEType::XZ, 8, 9}, {9, 1}}, - {{TQEType::YX, 8, 9}, {4, 13}}, {{TQEType::YY, 8, 9}, {7, 5}}, - {{TQEType::YZ, 8, 9}, {11, 1}}, {{TQEType::ZX, 8, 9}, {0, 9}}, - {{TQEType::ZY, 8, 9}, {2, 9}}, {{TQEType::ZZ, 8, 9}, {10, 9}}, - {{TQEType::XX, 10, 11}, {15, 14}}, {{TQEType::XY, 10, 11}, {14, 4}}, - {{TQEType::XZ, 10, 11}, {11, 1}}, {{TQEType::YX, 10, 11}, {5, 14}}, - {{TQEType::YY, 10, 11}, {6, 4}}, {{TQEType::YZ, 10, 11}, {9, 1}}, - {{TQEType::ZX, 10, 11}, {0, 11}}, {{TQEType::ZY, 10, 11}, {2, 11}}, - {{TQEType::ZZ, 10, 11}, {8, 11}}, {{TQEType::XX, 8, 11}, {13, 15}}, - {{TQEType::XY, 8, 11}, {12, 7}}, {{TQEType::XZ, 8, 11}, {9, 3}}, - {{TQEType::YX, 8, 11}, {7, 15}}, {{TQEType::YY, 8, 11}, {4, 7}}, - {{TQEType::YZ, 8, 11}, {11, 3}}, {{TQEType::ZX, 8, 11}, {2, 11}}, - {{TQEType::ZY, 8, 11}, {0, 11}}, {{TQEType::ZZ, 8, 11}, {10, 11}}, - {{TQEType::XX, 9, 10}, {12, 14}}, {{TQEType::XY, 9, 10}, {12, 6}}, - {{TQEType::XZ, 9, 10}, {9, 2}}, {{TQEType::YX, 9, 10}, {6, 15}}, - {{TQEType::YY, 9, 10}, {6, 5}}, {{TQEType::YZ, 9, 10}, {9, 0}}, - {{TQEType::ZX, 9, 10}, {3, 11}}, {{TQEType::ZY, 9, 10}, {3, 9}}, - {{TQEType::ZZ, 9, 10}, {9, 8}}, {{TQEType::XX, 11, 10}, {14, 15}}, - {{TQEType::XY, 11, 10}, {14, 5}}, {{TQEType::XZ, 11, 10}, {11, 0}}, - {{TQEType::YX, 11, 10}, {4, 14}}, {{TQEType::YY, 11, 10}, {4, 6}}, - {{TQEType::YZ, 11, 10}, {11, 2}}, {{TQEType::ZX, 11, 10}, {1, 11}}, - {{TQEType::ZY, 11, 10}, {1, 9}}, {{TQEType::ZZ, 11, 10}, {11, 8}}, - {{TQEType::XX, 9, 8}, {13, 12}}, {{TQEType::XY, 9, 8}, {13, 4}}, - {{TQEType::XZ, 9, 8}, {9, 0}}, {{TQEType::YX, 9, 8}, {5, 13}}, - {{TQEType::YY, 9, 8}, {5, 7}}, {{TQEType::YZ, 9, 8}, {9, 2}}, - {{TQEType::ZX, 9, 8}, {1, 9}}, {{TQEType::ZY, 9, 8}, {1, 11}}, - {{TQEType::ZZ, 9, 8}, {9, 10}}, {{TQEType::XX, 11, 8}, {15, 13}}, - {{TQEType::XY, 11, 8}, {15, 7}}, {{TQEType::XZ, 11, 8}, {11, 2}}, - {{TQEType::YX, 11, 8}, {7, 12}}, {{TQEType::YY, 11, 8}, {7, 4}}, - {{TQEType::YZ, 11, 8}, {11, 0}}, {{TQEType::ZX, 11, 8}, {3, 9}}, - {{TQEType::ZY, 11, 8}, {3, 11}}, {{TQEType::ZZ, 11, 8}, {11, 10}}, - {{TQEType::XX, 9, 9}, {13, 13}}, {{TQEType::XY, 9, 9}, {12, 5}}, - {{TQEType::XZ, 9, 9}, {8, 1}}, {{TQEType::YX, 9, 9}, {5, 12}}, - {{TQEType::YY, 9, 9}, {6, 6}}, {{TQEType::YZ, 9, 9}, {10, 3}}, - {{TQEType::ZX, 9, 9}, {1, 8}}, {{TQEType::ZY, 9, 9}, {3, 10}}, - {{TQEType::ZZ, 9, 9}, {11, 11}}, {{TQEType::XX, 11, 9}, {15, 12}}, - {{TQEType::XY, 11, 9}, {14, 6}}, {{TQEType::XZ, 11, 9}, {10, 3}}, - {{TQEType::YX, 11, 9}, {7, 13}}, {{TQEType::YY, 11, 9}, {4, 5}}, - {{TQEType::YZ, 11, 9}, {8, 1}}, {{TQEType::ZX, 11, 9}, {3, 8}}, - {{TQEType::ZY, 11, 9}, {1, 10}}, {{TQEType::ZZ, 11, 9}, {9, 11}}, - {{TQEType::XX, 9, 11}, {12, 15}}, {{TQEType::XY, 9, 11}, {13, 7}}, - {{TQEType::XZ, 9, 11}, {8, 3}}, {{TQEType::YX, 9, 11}, {6, 14}}, - {{TQEType::YY, 9, 11}, {5, 4}}, {{TQEType::YZ, 9, 11}, {10, 1}}, - {{TQEType::ZX, 9, 11}, {3, 10}}, {{TQEType::ZY, 9, 11}, {1, 8}}, - {{TQEType::ZZ, 9, 11}, {11, 9}}, {{TQEType::XX, 11, 11}, {14, 14}}, - {{TQEType::XY, 11, 11}, {15, 4}}, {{TQEType::XZ, 11, 11}, {10, 1}}, - {{TQEType::YX, 11, 11}, {4, 15}}, {{TQEType::YY, 11, 11}, {7, 7}}, - {{TQEType::YZ, 11, 11}, {8, 3}}, {{TQEType::ZX, 11, 11}, {1, 10}}, - {{TQEType::ZY, 11, 11}, {3, 8}}, {{TQEType::ZZ, 11, 11}, {9, 9}}, - {{TQEType::XX, 10, 10}, {15, 15}}, {{TQEType::XY, 10, 10}, {15, 5}}, - {{TQEType::XZ, 10, 10}, {10, 0}}, {{TQEType::YX, 10, 10}, {5, 15}}, - {{TQEType::YY, 10, 10}, {5, 5}}, {{TQEType::YZ, 10, 10}, {10, 0}}, - {{TQEType::ZX, 10, 10}, {0, 10}}, {{TQEType::ZY, 10, 10}, {0, 10}}, - {{TQEType::ZZ, 10, 10}, {10, 10}}, {{TQEType::XX, 8, 10}, {13, 14}}, - {{TQEType::XY, 8, 10}, {13, 6}}, {{TQEType::XZ, 8, 10}, {8, 2}}, - {{TQEType::YX, 8, 10}, {7, 14}}, {{TQEType::YY, 8, 10}, {7, 6}}, - {{TQEType::YZ, 8, 10}, {8, 2}}, {{TQEType::ZX, 8, 10}, {2, 10}}, - {{TQEType::ZY, 8, 10}, {2, 10}}, {{TQEType::ZZ, 8, 10}, {8, 10}}, - {{TQEType::XX, 10, 8}, {14, 13}}, {{TQEType::XY, 10, 8}, {14, 7}}, - {{TQEType::XZ, 10, 8}, {10, 2}}, {{TQEType::YX, 10, 8}, {6, 13}}, - {{TQEType::YY, 10, 8}, {6, 7}}, {{TQEType::YZ, 10, 8}, {10, 2}}, - {{TQEType::ZX, 10, 8}, {2, 8}}, {{TQEType::ZY, 10, 8}, {2, 8}}, - {{TQEType::ZZ, 10, 8}, {10, 8}}, {{TQEType::XX, 8, 8}, {12, 12}}, - {{TQEType::XY, 8, 8}, {12, 4}}, {{TQEType::XZ, 8, 8}, {8, 0}}, - {{TQEType::YX, 8, 8}, {4, 12}}, {{TQEType::YY, 8, 8}, {4, 4}}, - {{TQEType::YZ, 8, 8}, {8, 0}}, {{TQEType::ZX, 8, 8}, {0, 8}}, - {{TQEType::ZY, 8, 8}, {0, 8}}, {{TQEType::ZZ, 8, 8}, {8, 8}}, - {{TQEType::XX, 1, 9}, {5, 9}}, {{TQEType::XY, 1, 9}, {4, 9}}, - {{TQEType::XZ, 1, 9}, {0, 9}}, {{TQEType::YX, 1, 9}, {13, 8}}, - {{TQEType::YY, 1, 9}, {14, 10}}, {{TQEType::YZ, 1, 9}, {2, 11}}, - {{TQEType::ZX, 1, 9}, {9, 8}}, {{TQEType::ZY, 1, 9}, {11, 10}}, - {{TQEType::ZZ, 1, 9}, {3, 11}}, {{TQEType::XX, 3, 9}, {7, 8}}, - {{TQEType::XY, 3, 9}, {6, 10}}, {{TQEType::XZ, 3, 9}, {2, 11}}, - {{TQEType::YX, 3, 9}, {15, 9}}, {{TQEType::YY, 3, 9}, {12, 9}}, - {{TQEType::YZ, 3, 9}, {0, 9}}, {{TQEType::ZX, 3, 9}, {11, 8}}, - {{TQEType::ZY, 3, 9}, {9, 10}}, {{TQEType::ZZ, 3, 9}, {1, 11}}, - {{TQEType::XX, 2, 9}, {6, 8}}, {{TQEType::XY, 2, 9}, {7, 10}}, - {{TQEType::XZ, 2, 9}, {3, 11}}, {{TQEType::YX, 2, 9}, {14, 8}}, - {{TQEType::YY, 2, 9}, {13, 10}}, {{TQEType::YZ, 2, 9}, {1, 11}}, - {{TQEType::ZX, 2, 9}, {10, 9}}, {{TQEType::ZY, 2, 9}, {8, 9}}, - {{TQEType::ZZ, 2, 9}, {0, 9}}, {{TQEType::XX, 0, 9}, {4, 9}}, - {{TQEType::XY, 0, 9}, {5, 9}}, {{TQEType::XZ, 0, 9}, {1, 9}}, - {{TQEType::YX, 0, 9}, {12, 9}}, {{TQEType::YY, 0, 9}, {15, 9}}, - {{TQEType::YZ, 0, 9}, {3, 9}}, {{TQEType::ZX, 0, 9}, {8, 9}}, - {{TQEType::ZY, 0, 9}, {10, 9}}, {{TQEType::ZZ, 0, 9}, {2, 9}}, - {{TQEType::XX, 1, 11}, {4, 11}}, {{TQEType::XY, 1, 11}, {5, 11}}, - {{TQEType::XZ, 1, 11}, {0, 11}}, {{TQEType::YX, 1, 11}, {14, 10}}, - {{TQEType::YY, 1, 11}, {13, 8}}, {{TQEType::YZ, 1, 11}, {2, 9}}, - {{TQEType::ZX, 1, 11}, {11, 10}}, {{TQEType::ZY, 1, 11}, {9, 8}}, - {{TQEType::ZZ, 1, 11}, {3, 9}}, {{TQEType::XX, 3, 11}, {6, 10}}, - {{TQEType::XY, 3, 11}, {7, 8}}, {{TQEType::XZ, 3, 11}, {2, 9}}, - {{TQEType::YX, 3, 11}, {12, 11}}, {{TQEType::YY, 3, 11}, {15, 11}}, - {{TQEType::YZ, 3, 11}, {0, 11}}, {{TQEType::ZX, 3, 11}, {9, 10}}, - {{TQEType::ZY, 3, 11}, {11, 8}}, {{TQEType::ZZ, 3, 11}, {1, 9}}, - {{TQEType::XX, 2, 11}, {7, 10}}, {{TQEType::XY, 2, 11}, {6, 8}}, - {{TQEType::XZ, 2, 11}, {3, 9}}, {{TQEType::YX, 2, 11}, {13, 10}}, - {{TQEType::YY, 2, 11}, {14, 8}}, {{TQEType::YZ, 2, 11}, {1, 9}}, - {{TQEType::ZX, 2, 11}, {8, 11}}, {{TQEType::ZY, 2, 11}, {10, 11}}, - {{TQEType::ZZ, 2, 11}, {0, 11}}, {{TQEType::XX, 0, 11}, {5, 11}}, - {{TQEType::XY, 0, 11}, {4, 11}}, {{TQEType::XZ, 0, 11}, {1, 11}}, - {{TQEType::YX, 0, 11}, {15, 11}}, {{TQEType::YY, 0, 11}, {12, 11}}, - {{TQEType::YZ, 0, 11}, {3, 11}}, {{TQEType::ZX, 0, 11}, {10, 11}}, - {{TQEType::ZY, 0, 11}, {8, 11}}, {{TQEType::ZZ, 0, 11}, {2, 11}}, - {{TQEType::XX, 1, 10}, {4, 10}}, {{TQEType::XY, 1, 10}, {4, 10}}, - {{TQEType::XZ, 1, 10}, {1, 10}}, {{TQEType::YX, 1, 10}, {14, 11}}, - {{TQEType::YY, 1, 10}, {14, 9}}, {{TQEType::YZ, 1, 10}, {1, 8}}, - {{TQEType::ZX, 1, 10}, {11, 11}}, {{TQEType::ZY, 1, 10}, {11, 9}}, - {{TQEType::ZZ, 1, 10}, {1, 8}}, {{TQEType::XX, 3, 10}, {6, 11}}, - {{TQEType::XY, 3, 10}, {6, 9}}, {{TQEType::XZ, 3, 10}, {3, 8}}, - {{TQEType::YX, 3, 10}, {12, 10}}, {{TQEType::YY, 3, 10}, {12, 10}}, - {{TQEType::YZ, 3, 10}, {3, 10}}, {{TQEType::ZX, 3, 10}, {9, 11}}, - {{TQEType::ZY, 3, 10}, {9, 9}}, {{TQEType::ZZ, 3, 10}, {3, 8}}, - {{TQEType::XX, 2, 10}, {7, 11}}, {{TQEType::XY, 2, 10}, {7, 9}}, - {{TQEType::XZ, 2, 10}, {2, 8}}, {{TQEType::YX, 2, 10}, {13, 11}}, - {{TQEType::YY, 2, 10}, {13, 9}}, {{TQEType::YZ, 2, 10}, {2, 8}}, - {{TQEType::ZX, 2, 10}, {8, 10}}, {{TQEType::ZY, 2, 10}, {8, 10}}, - {{TQEType::ZZ, 2, 10}, {2, 10}}, {{TQEType::XX, 0, 10}, {5, 10}}, - {{TQEType::XY, 0, 10}, {5, 10}}, {{TQEType::XZ, 0, 10}, {0, 10}}, - {{TQEType::YX, 0, 10}, {15, 10}}, {{TQEType::YY, 0, 10}, {15, 10}}, - {{TQEType::YZ, 0, 10}, {0, 10}}, {{TQEType::ZX, 0, 10}, {10, 10}}, - {{TQEType::ZY, 0, 10}, {10, 10}}, {{TQEType::ZZ, 0, 10}, {0, 10}}, - {{TQEType::XX, 1, 8}, {5, 8}}, {{TQEType::XY, 1, 8}, {5, 8}}, - {{TQEType::XZ, 1, 8}, {1, 8}}, {{TQEType::YX, 1, 8}, {13, 9}}, - {{TQEType::YY, 1, 8}, {13, 11}}, {{TQEType::YZ, 1, 8}, {1, 10}}, - {{TQEType::ZX, 1, 8}, {9, 9}}, {{TQEType::ZY, 1, 8}, {9, 11}}, - {{TQEType::ZZ, 1, 8}, {1, 10}}, {{TQEType::XX, 3, 8}, {7, 9}}, - {{TQEType::XY, 3, 8}, {7, 11}}, {{TQEType::XZ, 3, 8}, {3, 10}}, - {{TQEType::YX, 3, 8}, {15, 8}}, {{TQEType::YY, 3, 8}, {15, 8}}, - {{TQEType::YZ, 3, 8}, {3, 8}}, {{TQEType::ZX, 3, 8}, {11, 9}}, - {{TQEType::ZY, 3, 8}, {11, 11}}, {{TQEType::ZZ, 3, 8}, {3, 10}}, - {{TQEType::XX, 2, 8}, {6, 9}}, {{TQEType::XY, 2, 8}, {6, 11}}, - {{TQEType::XZ, 2, 8}, {2, 10}}, {{TQEType::YX, 2, 8}, {14, 9}}, - {{TQEType::YY, 2, 8}, {14, 11}}, {{TQEType::YZ, 2, 8}, {2, 10}}, - {{TQEType::ZX, 2, 8}, {10, 8}}, {{TQEType::ZY, 2, 8}, {10, 8}}, - {{TQEType::ZZ, 2, 8}, {2, 8}}, {{TQEType::XX, 0, 8}, {4, 8}}, - {{TQEType::XY, 0, 8}, {4, 8}}, {{TQEType::XZ, 0, 8}, {0, 8}}, - {{TQEType::YX, 0, 8}, {12, 8}}, {{TQEType::YY, 0, 8}, {12, 8}}, - {{TQEType::YZ, 0, 8}, {0, 8}}, {{TQEType::ZX, 0, 8}, {8, 8}}, - {{TQEType::ZY, 0, 8}, {8, 8}}, {{TQEType::ZZ, 0, 8}, {0, 8}}, - {{TQEType::XX, 7, 1}, {7, 0}}, {{TQEType::XY, 7, 1}, {6, 2}}, - {{TQEType::XZ, 7, 1}, {6, 3}}, {{TQEType::YX, 7, 1}, {7, 5}}, - {{TQEType::YY, 7, 1}, {4, 13}}, {{TQEType::YZ, 7, 1}, {4, 9}}, - {{TQEType::ZX, 7, 1}, {7, 4}}, {{TQEType::ZY, 7, 1}, {5, 14}}, - {{TQEType::ZZ, 7, 1}, {5, 11}}, {{TQEType::XX, 6, 1}, {6, 0}}, - {{TQEType::XY, 6, 1}, {7, 2}}, {{TQEType::XZ, 6, 1}, {7, 3}}, - {{TQEType::YX, 6, 1}, {6, 4}}, {{TQEType::YY, 6, 1}, {5, 14}}, - {{TQEType::YZ, 6, 1}, {5, 11}}, {{TQEType::ZX, 6, 1}, {6, 5}}, - {{TQEType::ZY, 6, 1}, {4, 13}}, {{TQEType::ZZ, 6, 1}, {4, 9}}, - {{TQEType::XX, 7, 3}, {6, 2}}, {{TQEType::XY, 7, 3}, {7, 0}}, - {{TQEType::XZ, 7, 3}, {6, 1}}, {{TQEType::YX, 7, 3}, {4, 7}}, - {{TQEType::YY, 7, 3}, {7, 15}}, {{TQEType::YZ, 7, 3}, {4, 11}}, - {{TQEType::ZX, 7, 3}, {5, 6}}, {{TQEType::ZY, 7, 3}, {7, 12}}, - {{TQEType::ZZ, 7, 3}, {5, 9}}, {{TQEType::XX, 6, 3}, {7, 2}}, - {{TQEType::XY, 6, 3}, {6, 0}}, {{TQEType::XZ, 6, 3}, {7, 1}}, - {{TQEType::YX, 6, 3}, {5, 6}}, {{TQEType::YY, 6, 3}, {6, 12}}, - {{TQEType::YZ, 6, 3}, {5, 9}}, {{TQEType::ZX, 6, 3}, {4, 7}}, - {{TQEType::ZY, 6, 3}, {6, 15}}, {{TQEType::ZZ, 6, 3}, {4, 11}}, - {{TQEType::XX, 7, 2}, {6, 3}}, {{TQEType::XY, 7, 2}, {6, 1}}, - {{TQEType::XZ, 7, 2}, {7, 0}}, {{TQEType::YX, 7, 2}, {4, 6}}, - {{TQEType::YY, 7, 2}, {4, 14}}, {{TQEType::YZ, 7, 2}, {7, 10}}, - {{TQEType::ZX, 7, 2}, {5, 7}}, {{TQEType::ZY, 7, 2}, {5, 13}}, - {{TQEType::ZZ, 7, 2}, {7, 8}}, {{TQEType::XX, 6, 2}, {7, 3}}, - {{TQEType::XY, 6, 2}, {7, 1}}, {{TQEType::XZ, 6, 2}, {6, 0}}, - {{TQEType::YX, 6, 2}, {5, 7}}, {{TQEType::YY, 6, 2}, {5, 13}}, - {{TQEType::YZ, 6, 2}, {6, 8}}, {{TQEType::ZX, 6, 2}, {4, 6}}, - {{TQEType::ZY, 6, 2}, {4, 14}}, {{TQEType::ZZ, 6, 2}, {6, 10}}, - {{TQEType::XX, 7, 0}, {7, 1}}, {{TQEType::XY, 7, 0}, {7, 3}}, - {{TQEType::XZ, 7, 0}, {7, 2}}, {{TQEType::YX, 7, 0}, {7, 4}}, - {{TQEType::YY, 7, 0}, {7, 12}}, {{TQEType::YZ, 7, 0}, {7, 8}}, - {{TQEType::ZX, 7, 0}, {7, 5}}, {{TQEType::ZY, 7, 0}, {7, 15}}, - {{TQEType::ZZ, 7, 0}, {7, 10}}, {{TQEType::XX, 6, 0}, {6, 1}}, - {{TQEType::XY, 6, 0}, {6, 3}}, {{TQEType::XZ, 6, 0}, {6, 2}}, - {{TQEType::YX, 6, 0}, {6, 5}}, {{TQEType::YY, 6, 0}, {6, 15}}, - {{TQEType::YZ, 6, 0}, {6, 10}}, {{TQEType::ZX, 6, 0}, {6, 4}}, - {{TQEType::ZY, 6, 0}, {6, 12}}, {{TQEType::ZZ, 6, 0}, {6, 8}}, - {{TQEType::XX, 5, 1}, {5, 1}}, {{TQEType::XY, 5, 1}, {4, 1}}, - {{TQEType::XZ, 5, 1}, {4, 1}}, {{TQEType::YX, 5, 1}, {5, 4}}, - {{TQEType::YY, 5, 1}, {6, 14}}, {{TQEType::YZ, 5, 1}, {6, 11}}, - {{TQEType::ZX, 5, 1}, {5, 4}}, {{TQEType::ZY, 5, 1}, {7, 14}}, - {{TQEType::ZZ, 5, 1}, {7, 11}}, {{TQEType::XX, 4, 1}, {4, 1}}, - {{TQEType::XY, 4, 1}, {5, 1}}, {{TQEType::XZ, 4, 1}, {5, 1}}, - {{TQEType::YX, 4, 1}, {4, 5}}, {{TQEType::YY, 4, 1}, {7, 13}}, - {{TQEType::YZ, 4, 1}, {7, 9}}, {{TQEType::ZX, 4, 1}, {4, 5}}, - {{TQEType::ZY, 4, 1}, {6, 13}}, {{TQEType::ZZ, 4, 1}, {6, 9}}, - {{TQEType::XX, 5, 3}, {4, 3}}, {{TQEType::XY, 5, 3}, {5, 3}}, - {{TQEType::XZ, 5, 3}, {4, 3}}, {{TQEType::YX, 5, 3}, {6, 6}}, - {{TQEType::YY, 5, 3}, {5, 12}}, {{TQEType::YZ, 5, 3}, {6, 9}}, - {{TQEType::ZX, 5, 3}, {7, 6}}, {{TQEType::ZY, 5, 3}, {5, 12}}, - {{TQEType::ZZ, 5, 3}, {7, 9}}, {{TQEType::XX, 4, 3}, {5, 3}}, - {{TQEType::XY, 4, 3}, {4, 3}}, {{TQEType::XZ, 4, 3}, {5, 3}}, - {{TQEType::YX, 4, 3}, {7, 7}}, {{TQEType::YY, 4, 3}, {4, 15}}, - {{TQEType::YZ, 4, 3}, {7, 11}}, {{TQEType::ZX, 4, 3}, {6, 7}}, - {{TQEType::ZY, 4, 3}, {4, 15}}, {{TQEType::ZZ, 4, 3}, {6, 11}}, - {{TQEType::XX, 5, 2}, {4, 2}}, {{TQEType::XY, 5, 2}, {4, 2}}, - {{TQEType::XZ, 5, 2}, {5, 2}}, {{TQEType::YX, 5, 2}, {6, 7}}, - {{TQEType::YY, 5, 2}, {6, 13}}, {{TQEType::YZ, 5, 2}, {5, 8}}, - {{TQEType::ZX, 5, 2}, {7, 7}}, {{TQEType::ZY, 5, 2}, {7, 13}}, - {{TQEType::ZZ, 5, 2}, {5, 8}}, {{TQEType::XX, 4, 2}, {5, 2}}, - {{TQEType::XY, 4, 2}, {5, 2}}, {{TQEType::XZ, 4, 2}, {4, 2}}, - {{TQEType::YX, 4, 2}, {7, 6}}, {{TQEType::YY, 4, 2}, {7, 14}}, - {{TQEType::YZ, 4, 2}, {4, 10}}, {{TQEType::ZX, 4, 2}, {6, 6}}, - {{TQEType::ZY, 4, 2}, {6, 14}}, {{TQEType::ZZ, 4, 2}, {4, 10}}, - {{TQEType::XX, 5, 0}, {5, 0}}, {{TQEType::XY, 5, 0}, {5, 0}}, - {{TQEType::XZ, 5, 0}, {5, 0}}, {{TQEType::YX, 5, 0}, {5, 5}}, - {{TQEType::YY, 5, 0}, {5, 15}}, {{TQEType::YZ, 5, 0}, {5, 10}}, - {{TQEType::ZX, 5, 0}, {5, 5}}, {{TQEType::ZY, 5, 0}, {5, 15}}, - {{TQEType::ZZ, 5, 0}, {5, 10}}, {{TQEType::XX, 4, 0}, {4, 0}}, - {{TQEType::XY, 4, 0}, {4, 0}}, {{TQEType::XZ, 4, 0}, {4, 0}}, - {{TQEType::YX, 4, 0}, {4, 4}}, {{TQEType::YY, 4, 0}, {4, 12}}, - {{TQEType::YZ, 4, 0}, {4, 8}}, {{TQEType::ZX, 4, 0}, {4, 4}}, - {{TQEType::ZY, 4, 0}, {4, 12}}, {{TQEType::ZZ, 4, 0}, {4, 8}}, - {{TQEType::XX, 13, 1}, {13, 5}}, {{TQEType::XY, 13, 1}, {12, 13}}, - {{TQEType::XZ, 13, 1}, {12, 9}}, {{TQEType::YX, 13, 1}, {13, 0}}, - {{TQEType::YY, 13, 1}, {14, 2}}, {{TQEType::YZ, 13, 1}, {14, 3}}, - {{TQEType::ZX, 13, 1}, {13, 4}}, {{TQEType::ZY, 13, 1}, {15, 14}}, - {{TQEType::ZZ, 13, 1}, {15, 11}}, {{TQEType::XX, 14, 1}, {14, 4}}, - {{TQEType::XY, 14, 1}, {15, 14}}, {{TQEType::XZ, 14, 1}, {15, 11}}, - {{TQEType::YX, 14, 1}, {14, 0}}, {{TQEType::YY, 14, 1}, {13, 2}}, - {{TQEType::YZ, 14, 1}, {13, 3}}, {{TQEType::ZX, 14, 1}, {14, 5}}, - {{TQEType::ZY, 14, 1}, {12, 13}}, {{TQEType::ZZ, 14, 1}, {12, 9}}, - {{TQEType::XX, 13, 3}, {12, 7}}, {{TQEType::XY, 13, 3}, {13, 15}}, - {{TQEType::XZ, 13, 3}, {12, 11}}, {{TQEType::YX, 13, 3}, {14, 2}}, - {{TQEType::YY, 13, 3}, {13, 0}}, {{TQEType::YZ, 13, 3}, {14, 1}}, - {{TQEType::ZX, 13, 3}, {15, 6}}, {{TQEType::ZY, 13, 3}, {13, 12}}, - {{TQEType::ZZ, 13, 3}, {15, 9}}, {{TQEType::XX, 14, 3}, {15, 6}}, - {{TQEType::XY, 14, 3}, {14, 12}}, {{TQEType::XZ, 14, 3}, {15, 9}}, - {{TQEType::YX, 14, 3}, {13, 2}}, {{TQEType::YY, 14, 3}, {14, 0}}, - {{TQEType::YZ, 14, 3}, {13, 1}}, {{TQEType::ZX, 14, 3}, {12, 7}}, - {{TQEType::ZY, 14, 3}, {14, 15}}, {{TQEType::ZZ, 14, 3}, {12, 11}}, - {{TQEType::XX, 13, 2}, {12, 6}}, {{TQEType::XY, 13, 2}, {12, 14}}, - {{TQEType::XZ, 13, 2}, {13, 10}}, {{TQEType::YX, 13, 2}, {14, 3}}, - {{TQEType::YY, 13, 2}, {14, 1}}, {{TQEType::YZ, 13, 2}, {13, 0}}, - {{TQEType::ZX, 13, 2}, {15, 7}}, {{TQEType::ZY, 13, 2}, {15, 13}}, - {{TQEType::ZZ, 13, 2}, {13, 8}}, {{TQEType::XX, 14, 2}, {15, 7}}, - {{TQEType::XY, 14, 2}, {15, 13}}, {{TQEType::XZ, 14, 2}, {14, 8}}, - {{TQEType::YX, 14, 2}, {13, 3}}, {{TQEType::YY, 14, 2}, {13, 1}}, - {{TQEType::YZ, 14, 2}, {14, 0}}, {{TQEType::ZX, 14, 2}, {12, 6}}, - {{TQEType::ZY, 14, 2}, {12, 14}}, {{TQEType::ZZ, 14, 2}, {14, 10}}, - {{TQEType::XX, 13, 0}, {13, 4}}, {{TQEType::XY, 13, 0}, {13, 12}}, - {{TQEType::XZ, 13, 0}, {13, 8}}, {{TQEType::YX, 13, 0}, {13, 1}}, - {{TQEType::YY, 13, 0}, {13, 3}}, {{TQEType::YZ, 13, 0}, {13, 2}}, - {{TQEType::ZX, 13, 0}, {13, 5}}, {{TQEType::ZY, 13, 0}, {13, 15}}, - {{TQEType::ZZ, 13, 0}, {13, 10}}, {{TQEType::XX, 14, 0}, {14, 5}}, - {{TQEType::XY, 14, 0}, {14, 15}}, {{TQEType::XZ, 14, 0}, {14, 10}}, - {{TQEType::YX, 14, 0}, {14, 1}}, {{TQEType::YY, 14, 0}, {14, 3}}, - {{TQEType::YZ, 14, 0}, {14, 2}}, {{TQEType::ZX, 14, 0}, {14, 4}}, - {{TQEType::ZY, 14, 0}, {14, 12}}, {{TQEType::ZZ, 14, 0}, {14, 8}}, - {{TQEType::XX, 15, 1}, {15, 4}}, {{TQEType::XY, 15, 1}, {14, 14}}, - {{TQEType::XZ, 15, 1}, {14, 11}}, {{TQEType::YX, 15, 1}, {15, 1}}, - {{TQEType::YY, 15, 1}, {12, 1}}, {{TQEType::YZ, 15, 1}, {12, 1}}, - {{TQEType::ZX, 15, 1}, {15, 4}}, {{TQEType::ZY, 15, 1}, {13, 14}}, - {{TQEType::ZZ, 15, 1}, {13, 11}}, {{TQEType::XX, 12, 1}, {12, 5}}, - {{TQEType::XY, 12, 1}, {13, 13}}, {{TQEType::XZ, 12, 1}, {13, 9}}, - {{TQEType::YX, 12, 1}, {12, 1}}, {{TQEType::YY, 12, 1}, {15, 1}}, - {{TQEType::YZ, 12, 1}, {15, 1}}, {{TQEType::ZX, 12, 1}, {12, 5}}, - {{TQEType::ZY, 12, 1}, {14, 13}}, {{TQEType::ZZ, 12, 1}, {14, 9}}, - {{TQEType::XX, 15, 3}, {14, 6}}, {{TQEType::XY, 15, 3}, {15, 12}}, - {{TQEType::XZ, 15, 3}, {14, 9}}, {{TQEType::YX, 15, 3}, {12, 3}}, - {{TQEType::YY, 15, 3}, {15, 3}}, {{TQEType::YZ, 15, 3}, {12, 3}}, - {{TQEType::ZX, 15, 3}, {13, 6}}, {{TQEType::ZY, 15, 3}, {15, 12}}, - {{TQEType::ZZ, 15, 3}, {13, 9}}, {{TQEType::XX, 12, 3}, {13, 7}}, - {{TQEType::XY, 12, 3}, {12, 15}}, {{TQEType::XZ, 12, 3}, {13, 11}}, - {{TQEType::YX, 12, 3}, {15, 3}}, {{TQEType::YY, 12, 3}, {12, 3}}, - {{TQEType::YZ, 12, 3}, {15, 3}}, {{TQEType::ZX, 12, 3}, {14, 7}}, - {{TQEType::ZY, 12, 3}, {12, 15}}, {{TQEType::ZZ, 12, 3}, {14, 11}}, - {{TQEType::XX, 15, 2}, {14, 7}}, {{TQEType::XY, 15, 2}, {14, 13}}, - {{TQEType::XZ, 15, 2}, {15, 8}}, {{TQEType::YX, 15, 2}, {12, 2}}, - {{TQEType::YY, 15, 2}, {12, 2}}, {{TQEType::YZ, 15, 2}, {15, 2}}, - {{TQEType::ZX, 15, 2}, {13, 7}}, {{TQEType::ZY, 15, 2}, {13, 13}}, - {{TQEType::ZZ, 15, 2}, {15, 8}}, {{TQEType::XX, 12, 2}, {13, 6}}, - {{TQEType::XY, 12, 2}, {13, 14}}, {{TQEType::XZ, 12, 2}, {12, 10}}, - {{TQEType::YX, 12, 2}, {15, 2}}, {{TQEType::YY, 12, 2}, {15, 2}}, - {{TQEType::YZ, 12, 2}, {12, 2}}, {{TQEType::ZX, 12, 2}, {14, 6}}, - {{TQEType::ZY, 12, 2}, {14, 14}}, {{TQEType::ZZ, 12, 2}, {12, 10}}, - {{TQEType::XX, 15, 0}, {15, 5}}, {{TQEType::XY, 15, 0}, {15, 15}}, - {{TQEType::XZ, 15, 0}, {15, 10}}, {{TQEType::YX, 15, 0}, {15, 0}}, - {{TQEType::YY, 15, 0}, {15, 0}}, {{TQEType::YZ, 15, 0}, {15, 0}}, - {{TQEType::ZX, 15, 0}, {15, 5}}, {{TQEType::ZY, 15, 0}, {15, 15}}, - {{TQEType::ZZ, 15, 0}, {15, 10}}, {{TQEType::XX, 12, 0}, {12, 4}}, - {{TQEType::XY, 12, 0}, {12, 12}}, {{TQEType::XZ, 12, 0}, {12, 8}}, - {{TQEType::YX, 12, 0}, {12, 0}}, {{TQEType::YY, 12, 0}, {12, 0}}, - {{TQEType::YZ, 12, 0}, {12, 0}}, {{TQEType::ZX, 12, 0}, {12, 4}}, - {{TQEType::ZY, 12, 0}, {12, 12}}, {{TQEType::ZZ, 12, 0}, {12, 8}}, - {{TQEType::XX, 9, 1}, {9, 5}}, {{TQEType::XY, 9, 1}, {8, 13}}, - {{TQEType::XZ, 9, 1}, {8, 9}}, {{TQEType::YX, 9, 1}, {9, 4}}, - {{TQEType::YY, 9, 1}, {10, 14}}, {{TQEType::YZ, 9, 1}, {10, 11}}, - {{TQEType::ZX, 9, 1}, {9, 0}}, {{TQEType::ZY, 9, 1}, {11, 2}}, - {{TQEType::ZZ, 9, 1}, {11, 3}}, {{TQEType::XX, 11, 1}, {11, 4}}, - {{TQEType::XY, 11, 1}, {10, 14}}, {{TQEType::XZ, 11, 1}, {10, 11}}, - {{TQEType::YX, 11, 1}, {11, 5}}, {{TQEType::YY, 11, 1}, {8, 13}}, - {{TQEType::YZ, 11, 1}, {8, 9}}, {{TQEType::ZX, 11, 1}, {11, 0}}, - {{TQEType::ZY, 11, 1}, {9, 2}}, {{TQEType::ZZ, 11, 1}, {9, 3}}, - {{TQEType::XX, 9, 3}, {8, 7}}, {{TQEType::XY, 9, 3}, {9, 15}}, - {{TQEType::XZ, 9, 3}, {8, 11}}, {{TQEType::YX, 9, 3}, {10, 6}}, - {{TQEType::YY, 9, 3}, {9, 12}}, {{TQEType::YZ, 9, 3}, {10, 9}}, - {{TQEType::ZX, 9, 3}, {11, 2}}, {{TQEType::ZY, 9, 3}, {9, 0}}, - {{TQEType::ZZ, 9, 3}, {11, 1}}, {{TQEType::XX, 11, 3}, {10, 6}}, - {{TQEType::XY, 11, 3}, {11, 12}}, {{TQEType::XZ, 11, 3}, {10, 9}}, - {{TQEType::YX, 11, 3}, {8, 7}}, {{TQEType::YY, 11, 3}, {11, 15}}, - {{TQEType::YZ, 11, 3}, {8, 11}}, {{TQEType::ZX, 11, 3}, {9, 2}}, - {{TQEType::ZY, 11, 3}, {11, 0}}, {{TQEType::ZZ, 11, 3}, {9, 1}}, - {{TQEType::XX, 9, 2}, {8, 6}}, {{TQEType::XY, 9, 2}, {8, 14}}, - {{TQEType::XZ, 9, 2}, {9, 10}}, {{TQEType::YX, 9, 2}, {10, 7}}, - {{TQEType::YY, 9, 2}, {10, 13}}, {{TQEType::YZ, 9, 2}, {9, 8}}, - {{TQEType::ZX, 9, 2}, {11, 3}}, {{TQEType::ZY, 9, 2}, {11, 1}}, - {{TQEType::ZZ, 9, 2}, {9, 0}}, {{TQEType::XX, 11, 2}, {10, 7}}, - {{TQEType::XY, 11, 2}, {10, 13}}, {{TQEType::XZ, 11, 2}, {11, 8}}, - {{TQEType::YX, 11, 2}, {8, 6}}, {{TQEType::YY, 11, 2}, {8, 14}}, - {{TQEType::YZ, 11, 2}, {11, 10}}, {{TQEType::ZX, 11, 2}, {9, 3}}, - {{TQEType::ZY, 11, 2}, {9, 1}}, {{TQEType::ZZ, 11, 2}, {11, 0}}, - {{TQEType::XX, 9, 0}, {9, 4}}, {{TQEType::XY, 9, 0}, {9, 12}}, - {{TQEType::XZ, 9, 0}, {9, 8}}, {{TQEType::YX, 9, 0}, {9, 5}}, - {{TQEType::YY, 9, 0}, {9, 15}}, {{TQEType::YZ, 9, 0}, {9, 10}}, - {{TQEType::ZX, 9, 0}, {9, 1}}, {{TQEType::ZY, 9, 0}, {9, 3}}, - {{TQEType::ZZ, 9, 0}, {9, 2}}, {{TQEType::XX, 11, 0}, {11, 5}}, - {{TQEType::XY, 11, 0}, {11, 15}}, {{TQEType::XZ, 11, 0}, {11, 10}}, - {{TQEType::YX, 11, 0}, {11, 4}}, {{TQEType::YY, 11, 0}, {11, 12}}, - {{TQEType::YZ, 11, 0}, {11, 8}}, {{TQEType::ZX, 11, 0}, {11, 1}}, - {{TQEType::ZY, 11, 0}, {11, 3}}, {{TQEType::ZZ, 11, 0}, {11, 2}}, - {{TQEType::XX, 10, 1}, {10, 4}}, {{TQEType::XY, 10, 1}, {11, 14}}, - {{TQEType::XZ, 10, 1}, {11, 11}}, {{TQEType::YX, 10, 1}, {10, 4}}, - {{TQEType::YY, 10, 1}, {9, 14}}, {{TQEType::YZ, 10, 1}, {9, 11}}, - {{TQEType::ZX, 10, 1}, {10, 1}}, {{TQEType::ZY, 10, 1}, {8, 1}}, - {{TQEType::ZZ, 10, 1}, {8, 1}}, {{TQEType::XX, 8, 1}, {8, 5}}, - {{TQEType::XY, 8, 1}, {9, 13}}, {{TQEType::XZ, 8, 1}, {9, 9}}, - {{TQEType::YX, 8, 1}, {8, 5}}, {{TQEType::YY, 8, 1}, {11, 13}}, - {{TQEType::YZ, 8, 1}, {11, 9}}, {{TQEType::ZX, 8, 1}, {8, 1}}, - {{TQEType::ZY, 8, 1}, {10, 1}}, {{TQEType::ZZ, 8, 1}, {10, 1}}, - {{TQEType::XX, 10, 3}, {11, 6}}, {{TQEType::XY, 10, 3}, {10, 12}}, - {{TQEType::XZ, 10, 3}, {11, 9}}, {{TQEType::YX, 10, 3}, {9, 6}}, - {{TQEType::YY, 10, 3}, {10, 12}}, {{TQEType::YZ, 10, 3}, {9, 9}}, - {{TQEType::ZX, 10, 3}, {8, 3}}, {{TQEType::ZY, 10, 3}, {10, 3}}, - {{TQEType::ZZ, 10, 3}, {8, 3}}, {{TQEType::XX, 8, 3}, {9, 7}}, - {{TQEType::XY, 8, 3}, {8, 15}}, {{TQEType::XZ, 8, 3}, {9, 11}}, - {{TQEType::YX, 8, 3}, {11, 7}}, {{TQEType::YY, 8, 3}, {8, 15}}, - {{TQEType::YZ, 8, 3}, {11, 11}}, {{TQEType::ZX, 8, 3}, {10, 3}}, - {{TQEType::ZY, 8, 3}, {8, 3}}, {{TQEType::ZZ, 8, 3}, {10, 3}}, - {{TQEType::XX, 10, 2}, {11, 7}}, {{TQEType::XY, 10, 2}, {11, 13}}, - {{TQEType::XZ, 10, 2}, {10, 8}}, {{TQEType::YX, 10, 2}, {9, 7}}, - {{TQEType::YY, 10, 2}, {9, 13}}, {{TQEType::YZ, 10, 2}, {10, 8}}, - {{TQEType::ZX, 10, 2}, {8, 2}}, {{TQEType::ZY, 10, 2}, {8, 2}}, - {{TQEType::ZZ, 10, 2}, {10, 2}}, {{TQEType::XX, 8, 2}, {9, 6}}, - {{TQEType::XY, 8, 2}, {9, 14}}, {{TQEType::XZ, 8, 2}, {8, 10}}, - {{TQEType::YX, 8, 2}, {11, 6}}, {{TQEType::YY, 8, 2}, {11, 14}}, - {{TQEType::YZ, 8, 2}, {8, 10}}, {{TQEType::ZX, 8, 2}, {10, 2}}, - {{TQEType::ZY, 8, 2}, {10, 2}}, {{TQEType::ZZ, 8, 2}, {8, 2}}, - {{TQEType::XX, 10, 0}, {10, 5}}, {{TQEType::XY, 10, 0}, {10, 15}}, - {{TQEType::XZ, 10, 0}, {10, 10}}, {{TQEType::YX, 10, 0}, {10, 5}}, - {{TQEType::YY, 10, 0}, {10, 15}}, {{TQEType::YZ, 10, 0}, {10, 10}}, - {{TQEType::ZX, 10, 0}, {10, 0}}, {{TQEType::ZY, 10, 0}, {10, 0}}, - {{TQEType::ZZ, 10, 0}, {10, 0}}, {{TQEType::XX, 8, 0}, {8, 4}}, - {{TQEType::XY, 8, 0}, {8, 12}}, {{TQEType::XZ, 8, 0}, {8, 8}}, - {{TQEType::YX, 8, 0}, {8, 4}}, {{TQEType::YY, 8, 0}, {8, 12}}, - {{TQEType::YZ, 8, 0}, {8, 8}}, {{TQEType::ZX, 8, 0}, {8, 0}}, - {{TQEType::ZY, 8, 0}, {8, 0}}, {{TQEType::ZZ, 8, 0}, {8, 0}}, - {{TQEType::XX, 1, 1}, {1, 1}}, {{TQEType::XY, 1, 1}, {0, 1}}, - {{TQEType::XZ, 1, 1}, {0, 1}}, {{TQEType::YX, 1, 1}, {1, 0}}, - {{TQEType::YY, 1, 1}, {2, 2}}, {{TQEType::YZ, 1, 1}, {2, 3}}, - {{TQEType::ZX, 1, 1}, {1, 0}}, {{TQEType::ZY, 1, 1}, {3, 2}}, - {{TQEType::ZZ, 1, 1}, {3, 3}}, {{TQEType::XX, 3, 1}, {3, 0}}, - {{TQEType::XY, 3, 1}, {2, 2}}, {{TQEType::XZ, 3, 1}, {2, 3}}, - {{TQEType::YX, 3, 1}, {3, 1}}, {{TQEType::YY, 3, 1}, {0, 1}}, - {{TQEType::YZ, 3, 1}, {0, 1}}, {{TQEType::ZX, 3, 1}, {3, 0}}, - {{TQEType::ZY, 3, 1}, {1, 2}}, {{TQEType::ZZ, 3, 1}, {1, 3}}, - {{TQEType::XX, 2, 1}, {2, 0}}, {{TQEType::XY, 2, 1}, {3, 2}}, - {{TQEType::XZ, 2, 1}, {3, 3}}, {{TQEType::YX, 2, 1}, {2, 0}}, - {{TQEType::YY, 2, 1}, {1, 2}}, {{TQEType::YZ, 2, 1}, {1, 3}}, - {{TQEType::ZX, 2, 1}, {2, 1}}, {{TQEType::ZY, 2, 1}, {0, 1}}, - {{TQEType::ZZ, 2, 1}, {0, 1}}, {{TQEType::XX, 0, 1}, {0, 1}}, - {{TQEType::XY, 0, 1}, {1, 1}}, {{TQEType::XZ, 0, 1}, {1, 1}}, - {{TQEType::YX, 0, 1}, {0, 1}}, {{TQEType::YY, 0, 1}, {3, 1}}, - {{TQEType::YZ, 0, 1}, {3, 1}}, {{TQEType::ZX, 0, 1}, {0, 1}}, - {{TQEType::ZY, 0, 1}, {2, 1}}, {{TQEType::ZZ, 0, 1}, {2, 1}}, - {{TQEType::XX, 1, 3}, {0, 3}}, {{TQEType::XY, 1, 3}, {1, 3}}, - {{TQEType::XZ, 1, 3}, {0, 3}}, {{TQEType::YX, 1, 3}, {2, 2}}, - {{TQEType::YY, 1, 3}, {1, 0}}, {{TQEType::YZ, 1, 3}, {2, 1}}, - {{TQEType::ZX, 1, 3}, {3, 2}}, {{TQEType::ZY, 1, 3}, {1, 0}}, - {{TQEType::ZZ, 1, 3}, {3, 1}}, {{TQEType::XX, 3, 3}, {2, 2}}, - {{TQEType::XY, 3, 3}, {3, 0}}, {{TQEType::XZ, 3, 3}, {2, 1}}, - {{TQEType::YX, 3, 3}, {0, 3}}, {{TQEType::YY, 3, 3}, {3, 3}}, - {{TQEType::YZ, 3, 3}, {0, 3}}, {{TQEType::ZX, 3, 3}, {1, 2}}, - {{TQEType::ZY, 3, 3}, {3, 0}}, {{TQEType::ZZ, 3, 3}, {1, 1}}, - {{TQEType::XX, 2, 3}, {3, 2}}, {{TQEType::XY, 2, 3}, {2, 0}}, - {{TQEType::XZ, 2, 3}, {3, 1}}, {{TQEType::YX, 2, 3}, {1, 2}}, - {{TQEType::YY, 2, 3}, {2, 0}}, {{TQEType::YZ, 2, 3}, {1, 1}}, - {{TQEType::ZX, 2, 3}, {0, 3}}, {{TQEType::ZY, 2, 3}, {2, 3}}, - {{TQEType::ZZ, 2, 3}, {0, 3}}, {{TQEType::XX, 0, 3}, {1, 3}}, - {{TQEType::XY, 0, 3}, {0, 3}}, {{TQEType::XZ, 0, 3}, {1, 3}}, - {{TQEType::YX, 0, 3}, {3, 3}}, {{TQEType::YY, 0, 3}, {0, 3}}, - {{TQEType::YZ, 0, 3}, {3, 3}}, {{TQEType::ZX, 0, 3}, {2, 3}}, - {{TQEType::ZY, 0, 3}, {0, 3}}, {{TQEType::ZZ, 0, 3}, {2, 3}}, - {{TQEType::XX, 1, 2}, {0, 2}}, {{TQEType::XY, 1, 2}, {0, 2}}, - {{TQEType::XZ, 1, 2}, {1, 2}}, {{TQEType::YX, 1, 2}, {2, 3}}, - {{TQEType::YY, 1, 2}, {2, 1}}, {{TQEType::YZ, 1, 2}, {1, 0}}, - {{TQEType::ZX, 1, 2}, {3, 3}}, {{TQEType::ZY, 1, 2}, {3, 1}}, - {{TQEType::ZZ, 1, 2}, {1, 0}}, {{TQEType::XX, 3, 2}, {2, 3}}, - {{TQEType::XY, 3, 2}, {2, 1}}, {{TQEType::XZ, 3, 2}, {3, 0}}, - {{TQEType::YX, 3, 2}, {0, 2}}, {{TQEType::YY, 3, 2}, {0, 2}}, - {{TQEType::YZ, 3, 2}, {3, 2}}, {{TQEType::ZX, 3, 2}, {1, 3}}, - {{TQEType::ZY, 3, 2}, {1, 1}}, {{TQEType::ZZ, 3, 2}, {3, 0}}, - {{TQEType::XX, 2, 2}, {3, 3}}, {{TQEType::XY, 2, 2}, {3, 1}}, - {{TQEType::XZ, 2, 2}, {2, 0}}, {{TQEType::YX, 2, 2}, {1, 3}}, - {{TQEType::YY, 2, 2}, {1, 1}}, {{TQEType::YZ, 2, 2}, {2, 0}}, - {{TQEType::ZX, 2, 2}, {0, 2}}, {{TQEType::ZY, 2, 2}, {0, 2}}, - {{TQEType::ZZ, 2, 2}, {2, 2}}, {{TQEType::XX, 0, 2}, {1, 2}}, - {{TQEType::XY, 0, 2}, {1, 2}}, {{TQEType::XZ, 0, 2}, {0, 2}}, - {{TQEType::YX, 0, 2}, {3, 2}}, {{TQEType::YY, 0, 2}, {3, 2}}, - {{TQEType::YZ, 0, 2}, {0, 2}}, {{TQEType::ZX, 0, 2}, {2, 2}}, - {{TQEType::ZY, 0, 2}, {2, 2}}, {{TQEType::ZZ, 0, 2}, {0, 2}}, - {{TQEType::XX, 1, 0}, {1, 0}}, {{TQEType::XY, 1, 0}, {1, 0}}, - {{TQEType::XZ, 1, 0}, {1, 0}}, {{TQEType::YX, 1, 0}, {1, 1}}, - {{TQEType::YY, 1, 0}, {1, 3}}, {{TQEType::YZ, 1, 0}, {1, 2}}, - {{TQEType::ZX, 1, 0}, {1, 1}}, {{TQEType::ZY, 1, 0}, {1, 3}}, - {{TQEType::ZZ, 1, 0}, {1, 2}}, {{TQEType::XX, 3, 0}, {3, 1}}, - {{TQEType::XY, 3, 0}, {3, 3}}, {{TQEType::XZ, 3, 0}, {3, 2}}, - {{TQEType::YX, 3, 0}, {3, 0}}, {{TQEType::YY, 3, 0}, {3, 0}}, - {{TQEType::YZ, 3, 0}, {3, 0}}, {{TQEType::ZX, 3, 0}, {3, 1}}, - {{TQEType::ZY, 3, 0}, {3, 3}}, {{TQEType::ZZ, 3, 0}, {3, 2}}, - {{TQEType::XX, 2, 0}, {2, 1}}, {{TQEType::XY, 2, 0}, {2, 3}}, - {{TQEType::XZ, 2, 0}, {2, 2}}, {{TQEType::YX, 2, 0}, {2, 1}}, - {{TQEType::YY, 2, 0}, {2, 3}}, {{TQEType::YZ, 2, 0}, {2, 2}}, - {{TQEType::ZX, 2, 0}, {2, 0}}, {{TQEType::ZY, 2, 0}, {2, 0}}, - {{TQEType::ZZ, 2, 0}, {2, 0}}, {{TQEType::XX, 0, 0}, {0, 0}}, - {{TQEType::XY, 0, 0}, {0, 0}}, {{TQEType::XZ, 0, 0}, {0, 0}}, - {{TQEType::YX, 0, 0}, {0, 0}}, {{TQEType::YY, 0, 0}, {0, 0}}, - {{TQEType::YZ, 0, 0}, {0, 0}}, {{TQEType::ZX, 0, 0}, {0, 0}}, - {{TQEType::ZY, 0, 0}, {0, 0}}, {{TQEType::ZZ, 0, 0}, {0, 0}}}; + std::pair, std::vector, hash_pauli_pauli> + TQE_REDUCTION_MAP = { + {{Pauli::X, Pauli::X}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::ZX}}, + {{Pauli::X, Pauli::Y}, + {TQEType::XX, TQEType::XZ, TQEType::YY, TQEType::ZY}}, + {{Pauli::X, Pauli::Z}, + {TQEType::XX, TQEType::XY, TQEType::YZ, TQEType::ZZ}}, + {{Pauli::Y, Pauli::X}, + {TQEType::XX, TQEType::YY, TQEType::YZ, TQEType::ZX}}, + {{Pauli::Y, Pauli::Y}, + {TQEType::XY, TQEType::YX, TQEType::YZ, TQEType::ZY}}, + {{Pauli::Y, Pauli::Z}, + {TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZZ}}, + {{Pauli::Z, Pauli::X}, + {TQEType::XX, TQEType::YX, TQEType::ZY, TQEType::ZZ}}, + {{Pauli::Z, Pauli::Y}, + {TQEType::XY, TQEType::YY, TQEType::ZX, TQEType::ZZ}}, + {{Pauli::Z, Pauli::Z}, + {TQEType::XZ, TQEType::YZ, TQEType::ZX, TQEType::ZY}}}; /** - * @brief Maps a pair of week suppotrs in a factor support vector to a set of - * TQE gates that will transform them to a pair that only has one weak (not - * always possible) + * @brief Given (P(0), P(1), Q(0), Q(1)), + * where both pairs (P(0), Q(0)) and (P(1), Q(1)) non-trivially commute, + * return the TQE gates that can map one pair to identities. */ const static std::unordered_map< - std::pair, std::vector, hash_pair> - FACTOR_PAIR_WW_TO_WN_OR_NW_TQES = { - {{5, 5}, {TQEType::XY, TQEType::ZX, TQEType::YX, TQEType::XZ}}, - {{4, 5}, {}}, - {{5, 4}, {}}, - {{4, 4}, {TQEType::XY, TQEType::ZX, TQEType::YX, TQEType::XZ}}, - {{15, 5}, {TQEType::ZX, TQEType::YZ, TQEType::XX, TQEType::YY}}, - {{12, 5}, {}}, - {{15, 4}, {}}, - {{12, 4}, {TQEType::ZX, TQEType::YZ, TQEType::XX, TQEType::YY}}, - {{10, 5}, {TQEType::ZZ, TQEType::YX, TQEType::ZY, TQEType::XX}}, - {{8, 5}, {}}, - {{10, 4}, {}}, - {{8, 4}, {TQEType::ZZ, TQEType::YX, TQEType::ZY, TQEType::XX}}, - {{1, 5}, {}}, - {{3, 5}, {}}, - {{2, 5}, {}}, - {{1, 4}, {}}, - {{3, 4}, {}}, - {{2, 4}, {}}, - {{5, 15}, {TQEType::ZY, TQEType::XZ, TQEType::XX, TQEType::YY}}, - {{4, 15}, {}}, - {{5, 12}, {}}, - {{4, 12}, {TQEType::ZY, TQEType::XZ, TQEType::XX, TQEType::YY}}, - {{15, 15}, {TQEType::XY, TQEType::ZY, TQEType::YX, TQEType::YZ}}, - {{12, 15}, {}}, - {{15, 12}, {}}, - {{12, 12}, {TQEType::XY, TQEType::ZY, TQEType::YX, TQEType::YZ}}, - {{10, 15}, {TQEType::XY, TQEType::ZX, TQEType::ZZ, TQEType::YY}}, - {{8, 15}, {}}, - {{10, 12}, {}}, - {{8, 12}, {TQEType::XY, TQEType::ZX, TQEType::ZZ, TQEType::YY}}, - {{1, 15}, {}}, - {{3, 15}, {}}, - {{2, 15}, {}}, - {{1, 12}, {}}, - {{3, 12}, {}}, - {{2, 12}, {}}, - {{5, 10}, {TQEType::XY, TQEType::ZZ, TQEType::YZ, TQEType::XX}}, - {{4, 10}, {}}, - {{5, 8}, {}}, - {{4, 8}, {TQEType::XY, TQEType::ZZ, TQEType::YZ, TQEType::XX}}, - {{15, 10}, {TQEType::ZZ, TQEType::YX, TQEType::XZ, TQEType::YY}}, - {{12, 10}, {}}, - {{15, 8}, {}}, - {{12, 8}, {TQEType::ZZ, TQEType::YX, TQEType::XZ, TQEType::YY}}, - {{10, 10}, {TQEType::ZX, TQEType::YZ, TQEType::ZY, TQEType::XZ}}, - {{8, 10}, {}}, - {{10, 8}, {}}, - {{8, 8}, {TQEType::ZX, TQEType::YZ, TQEType::ZY, TQEType::XZ}}, - {{1, 10}, {}}, - {{3, 10}, {}}, - {{2, 10}, {}}, - {{1, 8}, {}}, - {{3, 8}, {}}, - {{2, 8}, {}}, - {{5, 1}, {}}, - {{4, 1}, {}}, - {{5, 3}, {}}, - {{4, 3}, {}}, - {{5, 2}, {}}, - {{4, 2}, {}}, - {{15, 1}, {}}, - {{12, 1}, {}}, - {{15, 3}, {}}, - {{12, 3}, {}}, - {{15, 2}, {}}, - {{12, 2}, {}}, - {{10, 1}, {}}, - {{8, 1}, {}}, - {{10, 3}, {}}, - {{8, 3}, {}}, - {{10, 2}, {}}, - {{8, 2}, {}}, - {{1, 1}, {TQEType::XY, TQEType::ZX, TQEType::YX, TQEType::XZ}}, - {{3, 1}, {TQEType::ZX, TQEType::YZ, TQEType::XX, TQEType::YY}}, - {{2, 1}, {TQEType::ZZ, TQEType::YX, TQEType::ZY, TQEType::XX}}, - {{1, 3}, {TQEType::ZY, TQEType::XZ, TQEType::XX, TQEType::YY}}, - {{3, 3}, {TQEType::XY, TQEType::ZY, TQEType::YX, TQEType::YZ}}, - {{2, 3}, {TQEType::XY, TQEType::ZX, TQEType::ZZ, TQEType::YY}}, - {{1, 2}, {TQEType::XY, TQEType::ZZ, TQEType::YZ, TQEType::XX}}, - {{3, 2}, {TQEType::ZZ, TQEType::YX, TQEType::XZ, TQEType::YY}}, - {{2, 2}, {TQEType::ZX, TQEType::YZ, TQEType::ZY, TQEType::XZ}}}; + std::tuple, std::vector, + hash_quadruple> + CC_TO_IC_OR_CI_MAP = { + {{Pauli::X, Pauli::X, Pauli::X, Pauli::X}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::ZX}}, + {{Pauli::X, Pauli::X, Pauli::X, Pauli::I}, {}}, + {{Pauli::X, Pauli::X, Pauli::I, Pauli::X}, {}}, + {{Pauli::X, Pauli::X, Pauli::I, Pauli::I}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::ZX}}, + {{Pauli::X, Pauli::Y, Pauli::X, Pauli::Y}, + {TQEType::XX, TQEType::XZ, TQEType::YY, TQEType::ZY}}, + {{Pauli::X, Pauli::Y, Pauli::X, Pauli::I}, {}}, + {{Pauli::X, Pauli::Y, Pauli::I, Pauli::Y}, {}}, + {{Pauli::X, Pauli::Y, Pauli::I, Pauli::I}, + {TQEType::XX, TQEType::XZ, TQEType::YY, TQEType::ZY}}, + {{Pauli::X, Pauli::Z, Pauli::X, Pauli::Z}, + {TQEType::XX, TQEType::XY, TQEType::YZ, TQEType::ZZ}}, + {{Pauli::X, Pauli::Z, Pauli::X, Pauli::I}, {}}, + {{Pauli::X, Pauli::Z, Pauli::I, Pauli::Z}, {}}, + {{Pauli::X, Pauli::Z, Pauli::I, Pauli::I}, + {TQEType::XX, TQEType::XY, TQEType::YZ, TQEType::ZZ}}, + {{Pauli::X, Pauli::I, Pauli::X, Pauli::X}, {}}, + {{Pauli::X, Pauli::I, Pauli::X, Pauli::Y}, {}}, + {{Pauli::X, Pauli::I, Pauli::X, Pauli::Z}, {}}, + {{Pauli::X, Pauli::I, Pauli::I, Pauli::X}, {}}, + {{Pauli::X, Pauli::I, Pauli::I, Pauli::Y}, {}}, + {{Pauli::X, Pauli::I, Pauli::I, Pauli::Z}, {}}, + {{Pauli::Y, Pauli::X, Pauli::Y, Pauli::X}, + {TQEType::XX, TQEType::YY, TQEType::YZ, TQEType::ZX}}, + {{Pauli::Y, Pauli::X, Pauli::Y, Pauli::I}, {}}, + {{Pauli::Y, Pauli::X, Pauli::I, Pauli::X}, {}}, + {{Pauli::Y, Pauli::X, Pauli::I, Pauli::I}, + {TQEType::XX, TQEType::YY, TQEType::YZ, TQEType::ZX}}, + {{Pauli::Y, Pauli::Y, Pauli::Y, Pauli::Y}, + {TQEType::XY, TQEType::YX, TQEType::YZ, TQEType::ZY}}, + {{Pauli::Y, Pauli::Y, Pauli::Y, Pauli::I}, {}}, + {{Pauli::Y, Pauli::Y, Pauli::I, Pauli::Y}, {}}, + {{Pauli::Y, Pauli::Y, Pauli::I, Pauli::I}, + {TQEType::XY, TQEType::YX, TQEType::YZ, TQEType::ZY}}, + {{Pauli::Y, Pauli::Z, Pauli::Y, Pauli::Z}, + {TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZZ}}, + {{Pauli::Y, Pauli::Z, Pauli::Y, Pauli::I}, {}}, + {{Pauli::Y, Pauli::Z, Pauli::I, Pauli::Z}, {}}, + {{Pauli::Y, Pauli::Z, Pauli::I, Pauli::I}, + {TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZZ}}, + {{Pauli::Y, Pauli::I, Pauli::Y, Pauli::X}, {}}, + {{Pauli::Y, Pauli::I, Pauli::Y, Pauli::Y}, {}}, + {{Pauli::Y, Pauli::I, Pauli::Y, Pauli::Z}, {}}, + {{Pauli::Y, Pauli::I, Pauli::I, Pauli::X}, {}}, + {{Pauli::Y, Pauli::I, Pauli::I, Pauli::Y}, {}}, + {{Pauli::Y, Pauli::I, Pauli::I, Pauli::Z}, {}}, + {{Pauli::Z, Pauli::X, Pauli::Z, Pauli::X}, + {TQEType::XX, TQEType::YX, TQEType::ZY, TQEType::ZZ}}, + {{Pauli::Z, Pauli::X, Pauli::Z, Pauli::I}, {}}, + {{Pauli::Z, Pauli::X, Pauli::I, Pauli::X}, {}}, + {{Pauli::Z, Pauli::X, Pauli::I, Pauli::I}, + {TQEType::XX, TQEType::YX, TQEType::ZY, TQEType::ZZ}}, + {{Pauli::Z, Pauli::Y, Pauli::Z, Pauli::Y}, + {TQEType::XY, TQEType::YY, TQEType::ZX, TQEType::ZZ}}, + {{Pauli::Z, Pauli::Y, Pauli::Z, Pauli::I}, {}}, + {{Pauli::Z, Pauli::Y, Pauli::I, Pauli::Y}, {}}, + {{Pauli::Z, Pauli::Y, Pauli::I, Pauli::I}, + {TQEType::XY, TQEType::YY, TQEType::ZX, TQEType::ZZ}}, + {{Pauli::Z, Pauli::Z, Pauli::Z, Pauli::Z}, + {TQEType::XZ, TQEType::YZ, TQEType::ZX, TQEType::ZY}}, + {{Pauli::Z, Pauli::Z, Pauli::Z, Pauli::I}, {}}, + {{Pauli::Z, Pauli::Z, Pauli::I, Pauli::Z}, {}}, + {{Pauli::Z, Pauli::Z, Pauli::I, Pauli::I}, + {TQEType::XZ, TQEType::YZ, TQEType::ZX, TQEType::ZY}}, + {{Pauli::Z, Pauli::I, Pauli::Z, Pauli::X}, {}}, + {{Pauli::Z, Pauli::I, Pauli::Z, Pauli::Y}, {}}, + {{Pauli::Z, Pauli::I, Pauli::Z, Pauli::Z}, {}}, + {{Pauli::Z, Pauli::I, Pauli::I, Pauli::X}, {}}, + {{Pauli::Z, Pauli::I, Pauli::I, Pauli::Y}, {}}, + {{Pauli::Z, Pauli::I, Pauli::I, Pauli::Z}, {}}, + {{Pauli::I, Pauli::X, Pauli::X, Pauli::X}, {}}, + {{Pauli::I, Pauli::X, Pauli::X, Pauli::I}, {}}, + {{Pauli::I, Pauli::X, Pauli::Y, Pauli::X}, {}}, + {{Pauli::I, Pauli::X, Pauli::Y, Pauli::I}, {}}, + {{Pauli::I, Pauli::X, Pauli::Z, Pauli::X}, {}}, + {{Pauli::I, Pauli::X, Pauli::Z, Pauli::I}, {}}, + {{Pauli::I, Pauli::Y, Pauli::X, Pauli::Y}, {}}, + {{Pauli::I, Pauli::Y, Pauli::X, Pauli::I}, {}}, + {{Pauli::I, Pauli::Y, Pauli::Y, Pauli::Y}, {}}, + {{Pauli::I, Pauli::Y, Pauli::Y, Pauli::I}, {}}, + {{Pauli::I, Pauli::Y, Pauli::Z, Pauli::Y}, {}}, + {{Pauli::I, Pauli::Y, Pauli::Z, Pauli::I}, {}}, + {{Pauli::I, Pauli::Z, Pauli::X, Pauli::Z}, {}}, + {{Pauli::I, Pauli::Z, Pauli::X, Pauli::I}, {}}, + {{Pauli::I, Pauli::Z, Pauli::Y, Pauli::Z}, {}}, + {{Pauli::I, Pauli::Z, Pauli::Y, Pauli::I}, {}}, + {{Pauli::I, Pauli::Z, Pauli::Z, Pauli::Z}, {}}, + {{Pauli::I, Pauli::Z, Pauli::Z, Pauli::I}, {}}, + {{Pauli::I, Pauli::I, Pauli::X, Pauli::X}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::ZX}}, + {{Pauli::I, Pauli::I, Pauli::X, Pauli::Y}, + {TQEType::XX, TQEType::XZ, TQEType::YY, TQEType::ZY}}, + {{Pauli::I, Pauli::I, Pauli::X, Pauli::Z}, + {TQEType::XX, TQEType::XY, TQEType::YZ, TQEType::ZZ}}, + {{Pauli::I, Pauli::I, Pauli::Y, Pauli::X}, + {TQEType::XX, TQEType::YY, TQEType::YZ, TQEType::ZX}}, + {{Pauli::I, Pauli::I, Pauli::Y, Pauli::Y}, + {TQEType::XY, TQEType::YX, TQEType::YZ, TQEType::ZY}}, + {{Pauli::I, Pauli::I, Pauli::Y, Pauli::Z}, + {TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZZ}}, + {{Pauli::I, Pauli::I, Pauli::Z, Pauli::X}, + {TQEType::XX, TQEType::YX, TQEType::ZY, TQEType::ZZ}}, + {{Pauli::I, Pauli::I, Pauli::Z, Pauli::Y}, + {TQEType::XY, TQEType::YY, TQEType::ZX, TQEType::ZZ}}, + {{Pauli::I, Pauli::I, Pauli::Z, Pauli::Z}, + {TQEType::XZ, TQEType::YZ, TQEType::ZX, TQEType::ZY}}}; /** - * @brief Maps a pair of strong supports in a factor support vector - * to a set of TQE gates that will transform them to a pair of weak supports + * @brief Given (P(0), P(1), Q(0), Q(1)), + * where both pairs (P(0), Q(0)) and (P(1), Q(1)) anti-commute, + * return the TQE gates that can map both to non-trivial commuting pairs. */ const static std::unordered_map< - std::pair, std::vector, hash_pair> - FACTOR_PAIR_SS_TO_WW_TQES = { - {{7, 7}, - {TQEType::YX, TQEType::ZY, TQEType::XY, TQEType::ZX, TQEType::YZ, - TQEType::XZ}}, - {{6, 7}, - {TQEType::YX, TQEType::YY, TQEType::XY, TQEType::ZX, TQEType::ZZ, - TQEType::XZ}}, - {{7, 6}, - {TQEType::YX, TQEType::YY, TQEType::XY, TQEType::ZX, TQEType::ZZ, - TQEType::XZ}}, - {{6, 6}, - {TQEType::YX, TQEType::ZY, TQEType::XY, TQEType::ZX, TQEType::YZ, - TQEType::XZ}}, - {{13, 7}, - {TQEType::ZY, TQEType::YY, TQEType::ZX, TQEType::YZ, TQEType::XZ, - TQEType::XX}}, - {{14, 7}, - {TQEType::YY, TQEType::XY, TQEType::ZX, TQEType::YZ, TQEType::ZZ, - TQEType::XX}}, - {{13, 6}, - {TQEType::YY, TQEType::XY, TQEType::ZX, TQEType::YZ, TQEType::ZZ, - TQEType::XX}}, - {{14, 6}, - {TQEType::ZY, TQEType::YY, TQEType::ZX, TQEType::YZ, TQEType::XZ, - TQEType::XX}}, - {{9, 7}, - {TQEType::YX, TQEType::ZY, TQEType::YY, TQEType::ZZ, TQEType::XZ, - TQEType::XX}}, - {{11, 7}, - {TQEType::YX, TQEType::ZY, TQEType::XY, TQEType::YZ, TQEType::ZZ, - TQEType::XX}}, - {{9, 6}, - {TQEType::YX, TQEType::ZY, TQEType::XY, TQEType::YZ, TQEType::ZZ, - TQEType::XX}}, - {{11, 6}, - {TQEType::YX, TQEType::ZY, TQEType::YY, TQEType::ZZ, TQEType::XZ, - TQEType::XX}}, - {{7, 13}, - {TQEType::ZY, TQEType::YY, TQEType::ZX, TQEType::YZ, TQEType::XZ, - TQEType::XX}}, - {{6, 13}, - {TQEType::YX, TQEType::ZY, TQEType::YY, TQEType::ZZ, TQEType::XZ, - TQEType::XX}}, - {{7, 14}, - {TQEType::YX, TQEType::ZY, TQEType::YY, TQEType::ZZ, TQEType::XZ, - TQEType::XX}}, - {{6, 14}, - {TQEType::ZY, TQEType::YY, TQEType::ZX, TQEType::YZ, TQEType::XZ, - TQEType::XX}}, - {{13, 13}, - {TQEType::YX, TQEType::ZY, TQEType::XY, TQEType::ZX, TQEType::YZ, - TQEType::XZ}}, - {{14, 13}, - {TQEType::YX, TQEType::ZY, TQEType::XY, TQEType::YZ, TQEType::ZZ, - TQEType::XX}}, - {{13, 14}, - {TQEType::YX, TQEType::ZY, TQEType::XY, TQEType::YZ, TQEType::ZZ, - TQEType::XX}}, - {{14, 14}, - {TQEType::YX, TQEType::ZY, TQEType::XY, TQEType::ZX, TQEType::YZ, - TQEType::XZ}}, - {{9, 13}, - {TQEType::YX, TQEType::YY, TQEType::XY, TQEType::ZX, TQEType::ZZ, - TQEType::XZ}}, - {{11, 13}, - {TQEType::YY, TQEType::XY, TQEType::ZX, TQEType::YZ, TQEType::ZZ, - TQEType::XX}}, - {{9, 14}, - {TQEType::YY, TQEType::XY, TQEType::ZX, TQEType::YZ, TQEType::ZZ, - TQEType::XX}}, - {{11, 14}, - {TQEType::YX, TQEType::YY, TQEType::XY, TQEType::ZX, TQEType::ZZ, - TQEType::XZ}}, - {{7, 9}, - {TQEType::YY, TQEType::XY, TQEType::ZX, TQEType::YZ, TQEType::ZZ, - TQEType::XX}}, - {{6, 9}, - {TQEType::YX, TQEType::ZY, TQEType::XY, TQEType::YZ, TQEType::ZZ, - TQEType::XX}}, - {{7, 11}, - {TQEType::YX, TQEType::ZY, TQEType::XY, TQEType::YZ, TQEType::ZZ, - TQEType::XX}}, - {{6, 11}, - {TQEType::YY, TQEType::XY, TQEType::ZX, TQEType::YZ, TQEType::ZZ, - TQEType::XX}}, - {{13, 9}, - {TQEType::YX, TQEType::YY, TQEType::XY, TQEType::ZX, TQEType::ZZ, - TQEType::XZ}}, - {{14, 9}, - {TQEType::YX, TQEType::ZY, TQEType::YY, TQEType::ZZ, TQEType::XZ, - TQEType::XX}}, - {{13, 11}, - {TQEType::YX, TQEType::ZY, TQEType::YY, TQEType::ZZ, TQEType::XZ, - TQEType::XX}}, - {{14, 11}, - {TQEType::YX, TQEType::YY, TQEType::XY, TQEType::ZX, TQEType::ZZ, - TQEType::XZ}}, - {{9, 9}, - {TQEType::YX, TQEType::ZY, TQEType::XY, TQEType::ZX, TQEType::YZ, - TQEType::XZ}}, - {{11, 9}, - {TQEType::ZY, TQEType::YY, TQEType::ZX, TQEType::YZ, TQEType::XZ, - TQEType::XX}}, - {{9, 11}, - {TQEType::ZY, TQEType::YY, TQEType::ZX, TQEType::YZ, TQEType::XZ, - TQEType::XX}}, - {{11, 11}, - {TQEType::YX, TQEType::ZY, TQEType::XY, TQEType::ZX, TQEType::YZ, - TQEType::XZ}}}; + std::tuple, std::vector, + hash_quadruple> + AA_TO_CC_MAP = { + {{Pauli::X, Pauli::X, Pauli::Y, Pauli::Y}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::YZ, TQEType::ZX, + TQEType::ZY}}, + {{Pauli::X, Pauli::X, Pauli::Y, Pauli::Z}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZX, + TQEType::ZZ}}, + {{Pauli::X, Pauli::X, Pauli::Z, Pauli::Y}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZX, + TQEType::ZZ}}, + {{Pauli::X, Pauli::X, Pauli::Z, Pauli::Z}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::YZ, TQEType::ZX, + TQEType::ZY}}, + {{Pauli::X, Pauli::Y, Pauli::Y, Pauli::X}, + {TQEType::XX, TQEType::XZ, TQEType::YY, TQEType::YZ, TQEType::ZX, + TQEType::ZY}}, + {{Pauli::X, Pauli::Y, Pauli::Y, Pauli::Z}, + {TQEType::XX, TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZY, + TQEType::ZZ}}, + {{Pauli::X, Pauli::Y, Pauli::Z, Pauli::X}, + {TQEType::XX, TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZY, + TQEType::ZZ}}, + {{Pauli::X, Pauli::Y, Pauli::Z, Pauli::Z}, + {TQEType::XX, TQEType::XZ, TQEType::YY, TQEType::YZ, TQEType::ZX, + TQEType::ZY}}, + {{Pauli::X, Pauli::Z, Pauli::Y, Pauli::X}, + {TQEType::XX, TQEType::XY, TQEType::YY, TQEType::YZ, TQEType::ZX, + TQEType::ZZ}}, + {{Pauli::X, Pauli::Z, Pauli::Y, Pauli::Y}, + {TQEType::XX, TQEType::XY, TQEType::YX, TQEType::YZ, TQEType::ZY, + TQEType::ZZ}}, + {{Pauli::X, Pauli::Z, Pauli::Z, Pauli::X}, + {TQEType::XX, TQEType::XY, TQEType::YX, TQEType::YZ, TQEType::ZY, + TQEType::ZZ}}, + {{Pauli::X, Pauli::Z, Pauli::Z, Pauli::Y}, + {TQEType::XX, TQEType::XY, TQEType::YY, TQEType::YZ, TQEType::ZX, + TQEType::ZZ}}, + {{Pauli::Y, Pauli::X, Pauli::X, Pauli::Y}, + {TQEType::XX, TQEType::XZ, TQEType::YY, TQEType::YZ, TQEType::ZX, + TQEType::ZY}}, + {{Pauli::Y, Pauli::X, Pauli::X, Pauli::Z}, + {TQEType::XX, TQEType::XY, TQEType::YY, TQEType::YZ, TQEType::ZX, + TQEType::ZZ}}, + {{Pauli::Y, Pauli::X, Pauli::Z, Pauli::Y}, + {TQEType::XX, TQEType::XY, TQEType::YY, TQEType::YZ, TQEType::ZX, + TQEType::ZZ}}, + {{Pauli::Y, Pauli::X, Pauli::Z, Pauli::Z}, + {TQEType::XX, TQEType::XZ, TQEType::YY, TQEType::YZ, TQEType::ZX, + TQEType::ZY}}, + {{Pauli::Y, Pauli::Y, Pauli::X, Pauli::X}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::YZ, TQEType::ZX, + TQEType::ZY}}, + {{Pauli::Y, Pauli::Y, Pauli::X, Pauli::Z}, + {TQEType::XX, TQEType::XY, TQEType::YX, TQEType::YZ, TQEType::ZY, + TQEType::ZZ}}, + {{Pauli::Y, Pauli::Y, Pauli::Z, Pauli::X}, + {TQEType::XX, TQEType::XY, TQEType::YX, TQEType::YZ, TQEType::ZY, + TQEType::ZZ}}, + {{Pauli::Y, Pauli::Y, Pauli::Z, Pauli::Z}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::YZ, TQEType::ZX, + TQEType::ZY}}, + {{Pauli::Y, Pauli::Z, Pauli::X, Pauli::X}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZX, + TQEType::ZZ}}, + {{Pauli::Y, Pauli::Z, Pauli::X, Pauli::Y}, + {TQEType::XX, TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZY, + TQEType::ZZ}}, + {{Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, + {TQEType::XX, TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZY, + TQEType::ZZ}}, + {{Pauli::Y, Pauli::Z, Pauli::Z, Pauli::Y}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZX, + TQEType::ZZ}}, + {{Pauli::Z, Pauli::X, Pauli::X, Pauli::Y}, + {TQEType::XX, TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZY, + TQEType::ZZ}}, + {{Pauli::Z, Pauli::X, Pauli::X, Pauli::Z}, + {TQEType::XX, TQEType::XY, TQEType::YX, TQEType::YZ, TQEType::ZY, + TQEType::ZZ}}, + {{Pauli::Z, Pauli::X, Pauli::Y, Pauli::Y}, + {TQEType::XX, TQEType::XY, TQEType::YX, TQEType::YZ, TQEType::ZY, + TQEType::ZZ}}, + {{Pauli::Z, Pauli::X, Pauli::Y, Pauli::Z}, + {TQEType::XX, TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZY, + TQEType::ZZ}}, + {{Pauli::Z, Pauli::Y, Pauli::X, Pauli::X}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZX, + TQEType::ZZ}}, + {{Pauli::Z, Pauli::Y, Pauli::X, Pauli::Z}, + {TQEType::XX, TQEType::XY, TQEType::YY, TQEType::YZ, TQEType::ZX, + TQEType::ZZ}}, + {{Pauli::Z, Pauli::Y, Pauli::Y, Pauli::X}, + {TQEType::XX, TQEType::XY, TQEType::YY, TQEType::YZ, TQEType::ZX, + TQEType::ZZ}}, + {{Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::YY, TQEType::ZX, + TQEType::ZZ}}, + {{Pauli::Z, Pauli::Z, Pauli::X, Pauli::X}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::YZ, TQEType::ZX, + TQEType::ZY}}, + {{Pauli::Z, Pauli::Z, Pauli::X, Pauli::Y}, + {TQEType::XX, TQEType::XZ, TQEType::YY, TQEType::YZ, TQEType::ZX, + TQEType::ZY}}, + {{Pauli::Z, Pauli::Z, Pauli::Y, Pauli::X}, + {TQEType::XX, TQEType::XZ, TQEType::YY, TQEType::YZ, TQEType::ZX, + TQEType::ZY}}, + {{Pauli::Z, Pauli::Z, Pauli::Y, Pauli::Y}, + {TQEType::XY, TQEType::XZ, TQEType::YX, TQEType::YZ, TQEType::ZX, + TQEType::ZY}}}; /** - * @brief Maps a strong support and a weak support in a factor support vector - * to a set of TQE gates that will transform the weak support to no support + * @brief Given (P(0), P(1), Q(0), Q(1)), + * where P(0), Q(0) anti-commute and P(1), Q(1) non-trivially commute (not both + * identity), return the TQE gate that maps P(1), Q(1) to identities. */ const static std::unordered_map< - std::pair, std::vector, hash_pair> - FACTOR_PAIR_SW_TO_SN_TQES = { - {{7, 5}, {TQEType::ZX}}, {{6, 5}, {TQEType::YX}}, - {{7, 4}, {TQEType::YX}}, {{6, 4}, {TQEType::ZX}}, - {{13, 5}, {TQEType::ZX}}, {{14, 5}, {TQEType::XX}}, - {{13, 4}, {TQEType::XX}}, {{14, 4}, {TQEType::ZX}}, - {{9, 5}, {TQEType::YX}}, {{11, 5}, {TQEType::XX}}, - {{9, 4}, {TQEType::XX}}, {{11, 4}, {TQEType::YX}}, - {{7, 15}, {TQEType::ZY}}, {{6, 15}, {TQEType::YY}}, - {{7, 12}, {TQEType::YY}}, {{6, 12}, {TQEType::ZY}}, - {{13, 15}, {TQEType::ZY}}, {{14, 15}, {TQEType::XY}}, - {{13, 12}, {TQEType::XY}}, {{14, 12}, {TQEType::ZY}}, - {{9, 15}, {TQEType::YY}}, {{11, 15}, {TQEType::XY}}, - {{9, 12}, {TQEType::XY}}, {{11, 12}, {TQEType::YY}}, - {{7, 10}, {TQEType::ZZ}}, {{6, 10}, {TQEType::YZ}}, - {{7, 8}, {TQEType::YZ}}, {{6, 8}, {TQEType::ZZ}}, - {{13, 10}, {TQEType::ZZ}}, {{14, 10}, {TQEType::XZ}}, - {{13, 8}, {TQEType::XZ}}, {{14, 8}, {TQEType::ZZ}}, - {{9, 10}, {TQEType::YZ}}, {{11, 10}, {TQEType::XZ}}, - {{9, 8}, {TQEType::XZ}}, {{11, 8}, {TQEType::YZ}}, - {{7, 1}, {TQEType::XX}}, {{6, 1}, {TQEType::XX}}, - {{7, 3}, {TQEType::XY}}, {{6, 3}, {TQEType::XY}}, - {{7, 2}, {TQEType::XZ}}, {{6, 2}, {TQEType::XZ}}, - {{13, 1}, {TQEType::YX}}, {{14, 1}, {TQEType::YX}}, - {{13, 3}, {TQEType::YY}}, {{14, 3}, {TQEType::YY}}, - {{13, 2}, {TQEType::YZ}}, {{14, 2}, {TQEType::YZ}}, - {{9, 1}, {TQEType::ZX}}, {{11, 1}, {TQEType::ZX}}, - {{9, 3}, {TQEType::ZY}}, {{11, 3}, {TQEType::ZY}}, - {{9, 2}, {TQEType::ZZ}}, {{11, 2}, {TQEType::ZZ}}}; + std::tuple, std::vector, + hash_quadruple> + AC_TO_AI_MAP = { + {{Pauli::X, Pauli::X, Pauli::Y, Pauli::X}, {TQEType::ZX}}, + {{Pauli::X, Pauli::X, Pauli::Y, Pauli::I}, {TQEType::YX}}, + {{Pauli::X, Pauli::X, Pauli::Z, Pauli::X}, {TQEType::YX}}, + {{Pauli::X, Pauli::X, Pauli::Z, Pauli::I}, {TQEType::ZX}}, + {{Pauli::X, Pauli::Y, Pauli::Y, Pauli::Y}, {TQEType::ZY}}, + {{Pauli::X, Pauli::Y, Pauli::Y, Pauli::I}, {TQEType::YY}}, + {{Pauli::X, Pauli::Y, Pauli::Z, Pauli::Y}, {TQEType::YY}}, + {{Pauli::X, Pauli::Y, Pauli::Z, Pauli::I}, {TQEType::ZY}}, + {{Pauli::X, Pauli::Z, Pauli::Y, Pauli::Z}, {TQEType::ZZ}}, + {{Pauli::X, Pauli::Z, Pauli::Y, Pauli::I}, {TQEType::YZ}}, + {{Pauli::X, Pauli::Z, Pauli::Z, Pauli::Z}, {TQEType::YZ}}, + {{Pauli::X, Pauli::Z, Pauli::Z, Pauli::I}, {TQEType::ZZ}}, + {{Pauli::X, Pauli::I, Pauli::Y, Pauli::X}, {TQEType::XX}}, + {{Pauli::X, Pauli::I, Pauli::Y, Pauli::Y}, {TQEType::XY}}, + {{Pauli::X, Pauli::I, Pauli::Y, Pauli::Z}, {TQEType::XZ}}, + {{Pauli::X, Pauli::I, Pauli::Z, Pauli::X}, {TQEType::XX}}, + {{Pauli::X, Pauli::I, Pauli::Z, Pauli::Y}, {TQEType::XY}}, + {{Pauli::X, Pauli::I, Pauli::Z, Pauli::Z}, {TQEType::XZ}}, + {{Pauli::Y, Pauli::X, Pauli::X, Pauli::X}, {TQEType::ZX}}, + {{Pauli::Y, Pauli::X, Pauli::X, Pauli::I}, {TQEType::XX}}, + {{Pauli::Y, Pauli::X, Pauli::Z, Pauli::X}, {TQEType::XX}}, + {{Pauli::Y, Pauli::X, Pauli::Z, Pauli::I}, {TQEType::ZX}}, + {{Pauli::Y, Pauli::Y, Pauli::X, Pauli::Y}, {TQEType::ZY}}, + {{Pauli::Y, Pauli::Y, Pauli::X, Pauli::I}, {TQEType::XY}}, + {{Pauli::Y, Pauli::Y, Pauli::Z, Pauli::Y}, {TQEType::XY}}, + {{Pauli::Y, Pauli::Y, Pauli::Z, Pauli::I}, {TQEType::ZY}}, + {{Pauli::Y, Pauli::Z, Pauli::X, Pauli::Z}, {TQEType::ZZ}}, + {{Pauli::Y, Pauli::Z, Pauli::X, Pauli::I}, {TQEType::XZ}}, + {{Pauli::Y, Pauli::Z, Pauli::Z, Pauli::Z}, {TQEType::XZ}}, + {{Pauli::Y, Pauli::Z, Pauli::Z, Pauli::I}, {TQEType::ZZ}}, + {{Pauli::Y, Pauli::I, Pauli::X, Pauli::X}, {TQEType::YX}}, + {{Pauli::Y, Pauli::I, Pauli::X, Pauli::Y}, {TQEType::YY}}, + {{Pauli::Y, Pauli::I, Pauli::X, Pauli::Z}, {TQEType::YZ}}, + {{Pauli::Y, Pauli::I, Pauli::Z, Pauli::X}, {TQEType::YX}}, + {{Pauli::Y, Pauli::I, Pauli::Z, Pauli::Y}, {TQEType::YY}}, + {{Pauli::Y, Pauli::I, Pauli::Z, Pauli::Z}, {TQEType::YZ}}, + {{Pauli::Z, Pauli::X, Pauli::X, Pauli::X}, {TQEType::YX}}, + {{Pauli::Z, Pauli::X, Pauli::X, Pauli::I}, {TQEType::XX}}, + {{Pauli::Z, Pauli::X, Pauli::Y, Pauli::X}, {TQEType::XX}}, + {{Pauli::Z, Pauli::X, Pauli::Y, Pauli::I}, {TQEType::YX}}, + {{Pauli::Z, Pauli::Y, Pauli::X, Pauli::Y}, {TQEType::YY}}, + {{Pauli::Z, Pauli::Y, Pauli::X, Pauli::I}, {TQEType::XY}}, + {{Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Y}, {TQEType::XY}}, + {{Pauli::Z, Pauli::Y, Pauli::Y, Pauli::I}, {TQEType::YY}}, + {{Pauli::Z, Pauli::Z, Pauli::X, Pauli::Z}, {TQEType::YZ}}, + {{Pauli::Z, Pauli::Z, Pauli::X, Pauli::I}, {TQEType::XZ}}, + {{Pauli::Z, Pauli::Z, Pauli::Y, Pauli::Z}, {TQEType::XZ}}, + {{Pauli::Z, Pauli::Z, Pauli::Y, Pauli::I}, {TQEType::YZ}}, + {{Pauli::Z, Pauli::I, Pauli::X, Pauli::X}, {TQEType::ZX}}, + {{Pauli::Z, Pauli::I, Pauli::X, Pauli::Y}, {TQEType::ZY}}, + {{Pauli::Z, Pauli::I, Pauli::X, Pauli::Z}, {TQEType::ZZ}}, + {{Pauli::Z, Pauli::I, Pauli::Y, Pauli::X}, {TQEType::ZX}}, + {{Pauli::Z, Pauli::I, Pauli::Y, Pauli::Y}, {TQEType::ZY}}, + {{Pauli::Z, Pauli::I, Pauli::Y, Pauli::Z}, {TQEType::ZZ}}}; } // namespace GreedyPauliSimp diff --git a/tket/src/Circuit/OpJson.cpp b/tket/src/Circuit/OpJson.cpp index 4774aecc9a..9a56098305 100644 --- a/tket/src/Circuit/OpJson.cpp +++ b/tket/src/Circuit/OpJson.cpp @@ -18,6 +18,7 @@ #include "tket/OpType/OpType.hpp" #include "tket/OpType/OpTypeFunctions.hpp" #include "tket/Ops/BarrierOp.hpp" +#include "tket/Ops/ClExpr.hpp" #include "tket/Ops/ClassicalOps.hpp" #include "tket/Ops/OpPtr.hpp" #include "tket/Utils/Json.hpp" @@ -34,6 +35,8 @@ void from_json(const nlohmann::json& j, Op_ptr& op) { op = Conditional::deserialize(j); } else if (optype == OpType::WASM) { op = WASMOp::deserialize(j); + } else if (optype == OpType::ClExpr) { + op = ClExprOp::deserialize(j); } else if (is_classical_type(optype)) { op = ClassicalOp::deserialize(j); } else if (is_gate_type(optype)) { diff --git a/tket/src/Circuit/basic_circ_manip.cpp b/tket/src/Circuit/basic_circ_manip.cpp index 8bae39c18b..ca3d27faa3 100644 --- a/tket/src/Circuit/basic_circ_manip.cpp +++ b/tket/src/Circuit/basic_circ_manip.cpp @@ -429,7 +429,7 @@ unit_map_t Circuit::flatten_registers() { for (const BoundaryElement& el : boundary.get()) { if (el.type() == UnitType::Qubit) { rename_map.insert({el.id_, Qubit(q_index++)}); - } else { + } else if (el.type() == UnitType::Bit) { rename_map.insert({el.id_, Bit(c_index++)}); } } diff --git a/tket/src/OpType/OpTypeFunctions.cpp b/tket/src/OpType/OpTypeFunctions.cpp index 5a564c3171..0c2a0c98a6 100644 --- a/tket/src/OpType/OpTypeFunctions.cpp +++ b/tket/src/OpType/OpTypeFunctions.cpp @@ -108,7 +108,7 @@ const OpTypeSet& all_classical_types() { OpType::CopyBits, OpType::RangePredicate, OpType::ExplicitPredicate, OpType::ExplicitModifier, OpType::MultiBit, OpType::WASM, - OpType::ClassicalExpBox, + OpType::ClassicalExpBox, OpType::ClExpr, }; static std::unique_ptr gates = std::make_unique(optypes); diff --git a/tket/src/OpType/OpTypeInfo.cpp b/tket/src/OpType/OpTypeInfo.cpp index 19e292bb70..b708903fa0 100644 --- a/tket/src/OpType/OpTypeInfo.cpp +++ b/tket/src/OpType/OpTypeInfo.cpp @@ -15,6 +15,7 @@ #include "tket/OpType/OpTypeInfo.hpp" #include +#include #include "tket/OpType/OpType.hpp" @@ -180,7 +181,8 @@ const std::map& optypeinfo() { {"ClassicalExpBox", "ClassicalExpBox", {}, std::nullopt}}, {OpType::MultiBit, {"MultiBit", "MultiBit", {}, std::nullopt}}, {OpType::UnitaryTableauBox, - {"UnitaryTableauBox", "UnitaryTableauBox", {}, std::nullopt}}}; + {"UnitaryTableauBox", "UnitaryTableauBox", {}, std::nullopt}}, + {OpType::ClExpr, {"ClExpr", "ClExpr", {}, std::nullopt}}}; static std::unique_ptr> opinfo = std::make_unique>(typeinfo); return *opinfo; diff --git a/tket/src/Ops/ClExpr.cpp b/tket/src/Ops/ClExpr.cpp new file mode 100644 index 0000000000..0e0f1fdd5a --- /dev/null +++ b/tket/src/Ops/ClExpr.cpp @@ -0,0 +1,445 @@ +// Copyright 2019-2024 Cambridge Quantum Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tket/Ops/ClExpr.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "tket/OpType/OpType.hpp" + +namespace tket { + +std::ostream& operator<<(std::ostream& os, ClOp fn) { + switch (fn) { + case ClOp::INVALID: + return os << "INVALID"; + case ClOp::BitAnd: + return os << "and"; + case ClOp::BitOr: + return os << "or"; + case ClOp::BitXor: + return os << "xor"; + case ClOp::BitEq: + return os << "eq"; + case ClOp::BitNeq: + return os << "neq"; + case ClOp::BitNot: + return os << "not"; + case ClOp::BitZero: + return os << "zero"; + case ClOp::BitOne: + return os << "one"; + case ClOp::RegAnd: + return os << "and"; + case ClOp::RegOr: + return os << "or"; + case ClOp::RegXor: + return os << "xor"; + case ClOp::RegEq: + return os << "eq"; + case ClOp::RegNeq: + return os << "neq"; + case ClOp::RegNot: + return os << "not"; + case ClOp::RegZero: + return os << "zero"; + case ClOp::RegOne: + return os << "one"; + case ClOp::RegLt: + return os << "lt"; + case ClOp::RegGt: + return os << "gt"; + case ClOp::RegLeq: + return os << "leq"; + case ClOp::RegGeq: + return os << "geq"; + case ClOp::RegAdd: + return os << "add"; + case ClOp::RegSub: + return os << "sub"; + case ClOp::RegMul: + return os << "mul"; + case ClOp::RegDiv: + return os << "div"; + case ClOp::RegPow: + return os << "pow"; + case ClOp::RegLsh: + return os << "lsh"; + case ClOp::RegRsh: + return os << "rsh"; + case ClOp::RegNeg: + return os << "neg"; + } + throw std::logic_error("Invalid data"); +} + +std::ostream& operator<<(std::ostream& os, const ClBitVar& var) { + return os << "b" << var.index; +} + +std::ostream& operator<<(std::ostream& os, const ClRegVar& var) { + return os << "r" << var.index; +} + +std::ostream& operator<<(std::ostream& os, const ClExprVar& var) { + if (const ClBitVar* bvar = std::get_if(&var)) { + return os << *bvar; + } else { + ClRegVar rvar = std::get(var); + return os << rvar; + } +} + +void to_json(nlohmann::json& j, const ClExprVar& var) { + nlohmann::json inner_j; + if (const ClBitVar* bvar = std::get_if(&var)) { + j["type"] = "bit"; + to_json(inner_j, *bvar); + } else { + j["type"] = "reg"; + ClRegVar rvar = std::get(var); + to_json(inner_j, rvar); + } + j["var"] = inner_j; +} + +void from_json(const nlohmann::json& j, ClExprVar& var) { + const std::string vartype = j.at("type").get(); + if (vartype == "bit") { + var = j.at("var").get(); + } else { + TKET_ASSERT(vartype == "reg"); + var = j.at("var").get(); + } +} + +std::ostream& operator<<(std::ostream& os, const ClExprTerm& term) { + if (const int* n = std::get_if(&term)) { + return os << *n; + } else { + ClExprVar var = std::get(term); + return os << var; + } +} + +void to_json(nlohmann::json& j, const ClExprTerm& term) { + nlohmann::json inner_j; + if (const int* n = std::get_if(&term)) { + j["type"] = "int"; + inner_j = *n; + } else { + j["type"] = "var"; + ClExprVar var = std::get(term); + to_json(inner_j, var); + } + j["term"] = inner_j; +} + +void from_json(const nlohmann::json& j, ClExprTerm& term) { + const std::string termtype = j.at("type").get(); + if (termtype == "int") { + term = j.at("term").get(); + } else { + TKET_ASSERT(termtype == "var"); + term = j.at("term").get(); + } +} + +std::ostream& operator<<(std::ostream& os, const ClExprArg& arg) { + if (const ClExprTerm* term = std::get_if(&arg)) { + return os << *term; + } else { + ClExpr expr = std::get(arg); + return os << expr; + } +} + +void to_json(nlohmann::json& j, const ClExprArg& arg) { + nlohmann::json inner_j; + if (const ClExprTerm* term = std::get_if(&arg)) { + j["type"] = "term"; + to_json(inner_j, *term); + } else { + j["type"] = "expr"; + ClExpr expr = std::get(arg); + to_json(inner_j, expr); + } + j["input"] = inner_j; +} + +void from_json(const nlohmann::json& j, ClExprArg& arg) { + const std::string inputtype = j.at("type").get(); + if (inputtype == "term") { + arg = j.at("input").get(); + } else { + TKET_ASSERT(inputtype == "expr"); + ClExpr expr; + from_json(j.at("input"), expr); + arg = expr; + } +} + +ClExpr::ClExpr() : ClExpr(ClOp::INVALID, {}) {} + +ClExpr::ClExpr(ClOp op, std::vector args) + : op(op), args(args), all_bit_vars(), all_reg_vars() { + for (const ClExprArg& input : args) { + if (std::holds_alternative(input)) { + ClExprTerm basic_input = std::get(input); + if (std::holds_alternative(basic_input)) { + ClExprVar var = std::get(basic_input); + if (std::holds_alternative(var)) { + ClBitVar bit_var = std::get(var); + all_bit_vars.insert(bit_var.index); + } else { + ClRegVar reg_var = std::get(var); + all_reg_vars.insert(reg_var.index); + } + } + } else { + ClExpr expr = std::get(input); + std::set expr_bit_vars = expr.all_bit_variables(); + std::set expr_reg_vars = expr.all_reg_variables(); + all_bit_vars.insert(expr_bit_vars.begin(), expr_bit_vars.end()); + all_reg_vars.insert(expr_reg_vars.begin(), expr_reg_vars.end()); + } + } +} + +bool ClExpr::operator==(const ClExpr& other) const { + return op == other.op && args == other.args; +} + +std::ostream& operator<<(std::ostream& os, const ClExpr& expr) { + os << expr.get_op() << "("; + const std::vector& args = expr.get_args(); + unsigned n_args = args.size(); + for (unsigned i = 0; i < n_args; i++) { + os << args[i]; + if (i + 1 < n_args) { + os << ", "; + } + } + os << ")"; + return os; +} + +ClOp ClExpr::get_op() const { return op; } + +std::vector ClExpr::get_args() const { return args; } + +std::set ClExpr::all_bit_variables() const { return all_bit_vars; } + +std::set ClExpr::all_reg_variables() const { return all_reg_vars; } + +void to_json(nlohmann::json& j, const ClExpr& expr) { + nlohmann::json j_op = expr.get_op(); + nlohmann::json j_args = expr.get_args(); + j["op"] = j_op; + j["args"] = j_args; +} + +void from_json(const nlohmann::json& j, ClExpr& expr) { + ClOp op = j.at("op").get(); + std::vector args = j.at("args").get>(); + expr = ClExpr(op, args); +} + +WiredClExpr::WiredClExpr() : WiredClExpr({}, {}, {}, {}) {} + +WiredClExpr::WiredClExpr( + const ClExpr& expr, const std::map& bit_posn, + const std::map>& reg_posn, + const std::vector output_posn) + : expr(expr), + bit_posn(bit_posn), + reg_posn(reg_posn), + output_posn(output_posn) { + std::set b; + std::set r; + std::set posns; + for (const auto& pair : bit_posn) { + b.insert(pair.first); + unsigned bit_pos = pair.second; + if (posns.contains(bit_pos)) { + throw ClExprWiringError("Invalid maps constructing WiredClExpr"); + } + posns.insert(bit_pos); + all_bit_posns.insert(bit_pos); + } + for (const auto& pair : reg_posn) { + r.insert(pair.first); + for (unsigned bit_pos : pair.second) { + if (posns.contains(bit_pos)) { + throw ClExprWiringError("Invalid maps constructing WiredClExpr"); + } + posns.insert(bit_pos); + } + all_reg_posns.insert(pair.second); + } + total_n_bits = posns.size(); + for (const unsigned& posn : output_posn) { + if (!posns.contains(posn)) { + total_n_bits++; + } + } + if (output_posn.size() == 1) { + // It mustn't be one of an input register of size > 1 + unsigned i = output_posn[0]; + for (const std::vector& reg : all_reg_posns) { + if (reg.size() > 1 && std::any_of( + reg.begin(), reg.end(), + [&i](const unsigned& j) { return i == j; })) { + throw ClExprWiringError( + "Output bit contained in a larger input register"); + } + } + } else { + // It must either be disjoint from everything or match one of the registers + if (std::any_of( + output_posn.begin(), output_posn.end(), + [&posns](const unsigned& j) { return posns.contains(j); })) { + if (!std::any_of( + all_reg_posns.begin(), all_reg_posns.end(), + [&output_posn](const std::vector& reg) { + return output_posn == reg; + })) { + throw ClExprWiringError("Output register inconsistent with inputs"); + } + } + } + if (b != expr.all_bit_variables()) { + throw ClExprWiringError( + "Mismatch of bit variables constructing WiredClExpr"); + } + if (r != expr.all_reg_variables()) { + throw ClExprWiringError( + "Mismatch of register variables constructing WiredClExpr"); + } +} + +bool WiredClExpr::operator==(const WiredClExpr& other) const { + return expr == other.expr && bit_posn == other.bit_posn && + reg_posn == other.reg_posn && output_posn == other.output_posn; +} + +std::ostream& operator<<(std::ostream& os, const WiredClExpr& expr) { + os << expr.expr << " ["; + unsigned n_vars = expr.bit_posn.size() + expr.reg_posn.size(); + unsigned i = 0; + for (const std::pair pair : expr.bit_posn) { + os << "b" << pair.first << ":" << pair.second; + i++; + if (i < n_vars) { + os << ", "; + } + } + for (const std::pair> pair : expr.reg_posn) { + os << "r" << pair.first << ":("; + unsigned reg_size = pair.second.size(); + for (unsigned j = 0; j < reg_size; j++) { + os << pair.second[j]; + if (j + 1 < reg_size) { + os << ","; + } + } + os << ")"; + i++; + if (i < n_vars) { + os << ", "; + } + } + os << " --> ("; + unsigned n_outs = expr.output_posn.size(); + for (unsigned i = 0; i < n_outs; i++) { + os << expr.output_posn[i]; + if (i + 1 < n_outs) { + os << ","; + } + } + os << ")]"; + return os; +} + +ClExpr WiredClExpr::get_expr() const { return expr; } + +std::map WiredClExpr::get_bit_posn() const { + return bit_posn; +} + +std::map> WiredClExpr::get_reg_posn() const { + return reg_posn; +} + +std::vector WiredClExpr::get_output_posn() const { + return output_posn; +} + +unsigned WiredClExpr::get_total_n_bits() const { return total_n_bits; } + +void to_json(nlohmann::json& j, const WiredClExpr& expr) { + nlohmann::json j_expr = expr.get_expr(); + nlohmann::json j_bit_posn = expr.get_bit_posn(); + nlohmann::json j_reg_posn = expr.get_reg_posn(); + nlohmann::json j_output_posn = expr.get_output_posn(); + j["expr"] = j_expr; + j["bit_posn"] = j_bit_posn; + j["reg_posn"] = j_reg_posn; + j["output_posn"] = j_output_posn; +} + +void from_json(const nlohmann::json& j, WiredClExpr& expr) { + ClExpr e = j.at("expr").get(); + std::map bit_posn = + j.at("bit_posn").get>(); + std::map> reg_posn = + j.at("reg_posn").get>>(); + std::vector output_posn = + j.at("output_posn").get>(); + expr = WiredClExpr(e, bit_posn, reg_posn, output_posn); +} + +ClExprOp::ClExprOp(const WiredClExpr& expr) : Op(OpType::ClExpr), expr(expr) {} + +Op_ptr ClExprOp::symbol_substitution(const SymEngine::map_basic_basic&) const { + return std::make_shared(*this); +} + +SymSet ClExprOp::free_symbols() const { return SymSet(); } + +op_signature_t ClExprOp::get_signature() const { + return op_signature_t(expr.get_total_n_bits(), EdgeType::Classical); +} + +WiredClExpr ClExprOp::get_wired_expr() const { return expr; } + +nlohmann::json ClExprOp::serialize() const { + nlohmann::json j; + j["type"] = get_type(); + j["expr"] = get_wired_expr(); + return j; +} + +Op_ptr ClExprOp::deserialize(const nlohmann::json& j) { + ClExprOp exprop{j.at("expr").get()}; + return std::make_shared(exprop); +} + +} // namespace tket diff --git a/tket/src/Predicates/CompilerPass.cpp b/tket/src/Predicates/CompilerPass.cpp index ccea6b246f..43691b0faa 100644 --- a/tket/src/Predicates/CompilerPass.cpp +++ b/tket/src/Predicates/CompilerPass.cpp @@ -500,7 +500,19 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { } else if (passname == "GreedyPauliSimp") { double discount_rate = content.at("discount_rate").get(); double depth_weight = content.at("depth_weight").get(); - pp = gen_greedy_pauli_simp(discount_rate, depth_weight); + // for backward compatibility + if (!content.contains("max_tqe_candidates")) { + pp = gen_greedy_pauli_simp(discount_rate, depth_weight); + } + unsigned max_tqe_candidates = + content.at("max_tqe_candidates").get(); + unsigned max_lookahead = content.at("max_lookahead").get(); + unsigned seed = content.at("seed").get(); + bool allow_zzphase = content.at("allow_zzphase").get(); + pp = gen_greedy_pauli_simp( + discount_rate, depth_weight, max_lookahead, max_tqe_candidates, seed, + allow_zzphase); + } else if (passname == "PauliSimp") { // SEQUENCE PASS - DESERIALIZABLE ONLY Transforms::PauliSynthStrat pss = diff --git a/tket/src/Predicates/PassGenerators.cpp b/tket/src/Predicates/PassGenerators.cpp index 469418a04f..610caabd84 100644 --- a/tket/src/Predicates/PassGenerators.cpp +++ b/tket/src/Predicates/PassGenerators.cpp @@ -1013,25 +1013,46 @@ PassPtr gen_synthesise_pauli_graph( return std::make_shared(seq); } -PassPtr gen_greedy_pauli_simp(double discount_rate, double depth_weight) { - Transform t = - Transforms::greedy_pauli_optimisation(discount_rate, depth_weight); - PredicatePtr ccontrol_pred = std::make_shared(); - PredicatePtr mid_pred = std::make_shared(); - OpTypeSet ins = {OpType::Z, OpType::X, OpType::Y, - OpType::S, OpType::Sdg, OpType::V, - OpType::Vdg, OpType::H, OpType::CX, - OpType::CY, OpType::CZ, OpType::SWAP, - OpType::Rz, OpType::Rx, OpType::Ry, - OpType::T, OpType::Tdg, OpType::ZZMax, - OpType::ZZPhase, OpType::PhaseGadget, OpType::XXPhase, - OpType::YYPhase, OpType::PauliExpBox, OpType::Measure, - OpType::PhasedX}; +PassPtr gen_greedy_pauli_simp( + double discount_rate, double depth_weight, unsigned max_lookahead, + unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase) { + Transform t = Transforms::greedy_pauli_optimisation( + discount_rate, depth_weight, max_lookahead, max_tqe_candidates, seed, + allow_zzphase); + OpTypeSet ins = { + OpType::Z, + OpType::X, + OpType::Y, + OpType::S, + OpType::Sdg, + OpType::V, + OpType::Vdg, + OpType::H, + OpType::CX, + OpType::CY, + OpType::CZ, + OpType::SWAP, + OpType::Rz, + OpType::Rx, + OpType::Ry, + OpType::T, + OpType::Tdg, + OpType::ZZMax, + OpType::ZZPhase, + OpType::PhaseGadget, + OpType::XXPhase, + OpType::YYPhase, + OpType::Measure, + OpType::PhasedX, + OpType::Reset, + OpType::Conditional, + OpType::PauliExpBox, + OpType::PauliExpPairBox, + OpType::PauliExpCommutingSetBox}; + + ins.insert(all_classical_types().begin(), all_classical_types().end()); PredicatePtr in_gates = std::make_shared(ins); - PredicatePtrMap precons{ - CompilationUnit::make_type_pair(ccontrol_pred), - CompilationUnit::make_type_pair(mid_pred), - CompilationUnit::make_type_pair(in_gates)}; + PredicatePtrMap precons{CompilationUnit::make_type_pair(in_gates)}; PredicateClassGuarantees g_postcons = { {typeid(ConnectivityPredicate), Guarantee::Clear}, {typeid(NoWireSwapsPredicate), Guarantee::Clear}}; @@ -1042,6 +1063,10 @@ PassPtr gen_greedy_pauli_simp(double discount_rate, double depth_weight) { j["name"] = "GreedyPauliSimp"; j["discount_rate"] = discount_rate; j["depth_weight"] = depth_weight; + j["max_lookahead"] = max_lookahead; + j["max_tqe_candidates"] = max_tqe_candidates; + j["seed"] = seed; + j["allow_zzphase"] = allow_zzphase; return std::make_shared(precons, t, postcon, j); } diff --git a/tket/src/Transformations/GreedyPauliConverters.cpp b/tket/src/Transformations/GreedyPauliConverters.cpp new file mode 100644 index 0000000000..86cbdc56db --- /dev/null +++ b/tket/src/Transformations/GreedyPauliConverters.cpp @@ -0,0 +1,524 @@ +// Copyright 2019-2024 Cambridge Quantum Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "tket/Circuit/PauliExpBoxes.hpp" +#include "tket/OpType/OpType.hpp" +#include "tket/Transformations/GreedyPauliOptimisation.hpp" +#include "tket/Transformations/Transform.hpp" + +namespace tket { + +namespace Transforms { + +namespace GreedyPauliSimp { + +// convert a Clifford tableau to a vector of PauliNode_ptr +static std::vector get_nodes_from_tableau( + const UnitaryRevTableau& tab, unsigned n_qubits) { + std::vector rows; + for (unsigned i = 0; i < n_qubits; i++) { + Qubit q(i); + SpPauliStabiliser z_stab = tab.get_zrow(q); + SpPauliStabiliser x_stab = tab.get_xrow(q); + bool z_sign = cast_coeff(z_stab.coeff) == 1.; + bool x_sign = cast_coeff(x_stab.coeff) == 1.; + TKET_ASSERT(z_stab.string.size() == n_qubits); + std::vector z_string; + std::vector x_string; + for (unsigned j = 0; j < n_qubits; j++) { + z_string.push_back(z_stab.string.at(Qubit(j))); + x_string.push_back(x_stab.string.at(Qubit(j))); + } + rows.push_back(std::make_shared( + z_string, x_string, z_sign, x_sign, i)); + } + return rows; +} + +std::tuple, std::vector> +gpg_from_unordered_set(const std::vector& unordered_set) { + std::vector rotation_set; + unsigned n_qubits = unordered_set.at(0).string.size(); + for (auto& pauli : unordered_set) { + TKET_ASSERT(pauli.string.size() == n_qubits); + rotation_set.push_back( + std::make_shared(pauli.string, true, pauli.coeff)); + } + UnitaryRevTableau tab(n_qubits); + std::vector rows = get_nodes_from_tableau(tab, n_qubits); + return {rotation_set, rows}; +} + +// given a stabiliser Pauli string and an angle, return a dense string and an +// angle +static std::pair, Expr> dense_pauli( + const SpPauliStabiliser& pauli, const unsigned& n_qubits, + const Expr& angle) { + bool sign = cast_coeff(pauli.coeff) == 1.; + std::vector string(n_qubits, Pauli::I); + for (const auto& pair : pauli.string) { + string[pair.first.index().at(0)] = pair.second; + } + return {string, sign ? angle : -angle}; +} + +static bool strings_commute( + const std::vector& s1, const std::vector& s2) { + unsigned n_conflicts = 0; + TKET_ASSERT(s1.size() == s2.size()); + for (unsigned i = 0; i < s1.size(); ++i) { + Pauli p = s1[i]; + Pauli p2 = s2[i]; + if (p != Pauli::I && p2 != Pauli::I && p != p2) n_conflicts++; + } + return (n_conflicts % 2) == 0; +} + +static bool nodes_commute(const PauliNode_ptr& n1, const PauliNode_ptr& n2) { + CommuteInfo c1 = n1->get_commute_info(); + CommuteInfo c2 = n2->get_commute_info(); + // check if every string in n1 commutes with all strings in n2 + for (const std::vector& p1 : c1.paulis) { + for (const std::vector& p2 : c2.paulis) { + if (!strings_commute(p1, p2)) return false; + } + } + // check if the bits commute + for (const std::pair& b1 : c1.bits_info) { + for (const std::pair& b2 : c2.bits_info) { + if (b1.first == b2.first) { + // if two nodes read the same bit it's OK + if (b1.second == BitType::READ && b2.second == BitType::READ) { + break; + } + return false; + } + } + } + return true; +} + +GPGraph::GPGraph(const Circuit& circ) + : n_qubits_(circ.n_qubits()), n_bits_(circ.n_bits()) { + qubit_vector_t qubits = circ.all_qubits(); + bit_vector_t bits = circ.all_bits(); + for (const Qubit& q : qubits) { + TKET_ASSERT(q.reg_name() == q_default_reg()); + TKET_ASSERT(q.index().at(0) < qubits.size()); + } + for (const Bit& b : bits) { + TKET_ASSERT(b.reg_name() == c_default_reg()); + TKET_ASSERT(b.index().at(0) < bits.size()); + } + cliff_ = UnitaryRevTableau(n_qubits_); + for (const Command& cmd : circ.get_commands()) { + apply_gate_at_end(cmd); + } +} + +GPVertSet GPGraph::get_successors(const GPVert& vert) const { + GPVertSet succs; + for (auto iter = boost::adjacent_vertices(vert, graph_); + iter.first != iter.second; iter.first++) { + succs.insert(*iter.first); + } + return succs; +} + +GPVertSet GPGraph::get_predecessors(const GPVert& vert) const { + GPVertSet preds; + for (auto iter = boost::inv_adjacent_vertices(vert, graph_); + iter.first != iter.second; iter.first++) { + preds.insert(*iter.first); + } + return preds; +} + +// Adapted from `PauliGraph`, when adding a node to the graph, we check if it +// can be merged with an existing node. +void GPGraph::apply_node_at_end(PauliNode_ptr& node) { + GPVertSet to_search = end_line_; + GPVertSet commuted; + GPVert new_vert = boost::add_vertex(graph_); + graph_[new_vert] = node; + while (!to_search.empty()) { + // Get next candidate parent + GPVert to_compare = *to_search.begin(); + to_search.erase(to_search.begin()); + // Check that we have already commuted past all of its children + bool ready = true; + for (const GPVert& child : get_successors(to_compare)) { + if (commuted.get().find(child) == commuted.get().end()) { + ready = false; + break; + } + } + if (!ready) continue; + // Check if we can commute past it + PauliNode_ptr compare_node = graph_[to_compare]; + // merge two ConditionalBlocks if they share the same condition + // this sacrifices the ability to commute the node but can group operations + // for better optimisation + if (node->get_type() == PauliNodeType::ConditionalBlock && + compare_node->get_type() == PauliNodeType::ConditionalBlock) { + const ConditionalBlock& block1 = + dynamic_cast(*node); + ConditionalBlock& block2 = dynamic_cast(*compare_node); + if (block1.cond_bits() == block2.cond_bits() && + block1.cond_value() == block2.cond_value()) { + block2.append(block1); + boost::clear_vertex(new_vert, graph_); + boost::remove_vertex(new_vert, graph_); + return; + } + } + if (nodes_commute(node, compare_node)) { + // Check if two pauli rotations can be merged + if (node->get_type() == PauliNodeType::PauliRotation && + compare_node->get_type() == PauliNodeType::PauliRotation) { + const PauliRotation& rot1 = dynamic_cast(*node); + const PauliRotation& rot2 = + dynamic_cast(*compare_node); + if (rot1.string() == rot2.string()) { + boost::clear_vertex(new_vert, graph_); + boost::remove_vertex(new_vert, graph_); + Expr merged_angle = rot1.angle() + rot2.angle(); + std::optional cl_ang = equiv_Clifford(merged_angle); + if (cl_ang) { + cliff_.apply_pauli_at_front( + SpPauliStabiliser(rot1.string()), *cl_ang); + start_line_.erase(to_compare); + for (const GPVert& v : get_predecessors(to_compare)) { + if (boost::out_degree(v, graph_) == 1) { + end_line_.insert(v); + } + } + end_line_.erase(to_compare); + boost::clear_vertex(to_compare, graph_); + boost::remove_vertex(to_compare, graph_); + } else { + graph_[to_compare] = std::make_shared( + rot1.string(), true, merged_angle); + } + return; + } + } + // Commute and continue searching + GPVertSet preds = get_predecessors(to_compare); + to_search.insert(preds.begin(), preds.end()); + commuted.insert(to_compare); + } else { + // Does not commute - add dependency edge + boost::add_edge(to_compare, new_vert, graph_); + end_line_.erase(to_compare); + } + } + end_line_.insert(new_vert); + if (get_predecessors(new_vert).empty()) start_line_.insert(new_vert); +} + +void GPGraph::apply_paulis_at_end( + const std::vector, Expr>>& rotations, + const qubit_vector_t& qbs, bool conditional, + std::vector cond_bits, unsigned cond_value) { + std::vector, bool, Expr>> conj_rotations; + for (const auto& pair : rotations) { + const std::vector& paulis = pair.first; + const Expr& angle = pair.second; + // Note that global phase is ignored + if (static_cast(std::count( + paulis.begin(), paulis.end(), Pauli::I)) == paulis.size()) + continue; + std::optional cliff_angle = equiv_Clifford(angle); + if (cliff_angle && cliff_angle.value() == 0) continue; + QubitPauliMap qpm; + for (unsigned i = 0; i != qbs.size(); ++i) + qpm.insert({Qubit(qbs[i]), paulis[i]}); + if (cliff_angle && !conditional) { + cliff_.apply_pauli_at_end(SpPauliStabiliser(qpm), *cliff_angle); + continue; + } + // if not clifford we conjugate the string with the end-circuit tableau + SpPauliStabiliser qpt = cliff_.get_row_product(SpPauliStabiliser(qpm)); + auto [pauli_dense, theta] = dense_pauli(qpt, n_qubits_, angle); + conj_rotations.push_back({pauli_dense, true, theta}); + } + + // if conditional we add a ConditionalBlock otherwise we add individual + // rotations. + if (conditional) { + PauliNode_ptr node = std::make_shared( + conj_rotations, cond_bits, cond_value); + apply_node_at_end(node); + } else { + for (const auto& t : conj_rotations) { + PauliNode_ptr node = std::make_shared( + std::get<0>(t), std::get<1>(t), std::get<2>(t)); + apply_node_at_end(node); + } + } +} + +void GPGraph::apply_gate_at_end( + const Command& cmd, bool conditional, std::vector cond_bits, + unsigned cond_value) { + const Op_ptr op = cmd.get_op_ptr(); + unit_vector_t args = cmd.get_args(); + qubit_vector_t qbs = cmd.get_qubits(); + OpType type = op->get_type(); + + for (const UnitID& arg : args) { + if (arg.type() == UnitType::Qubit) { + auto it = end_measures_.left.find(arg.index().at(0)); + if (it != end_measures_.left.end()) { + // the measurement is no longer end-circuit, we remove it from + // end_measures_ and add it as a MidMeasure node instead. + SpPauliStabiliser paulis = cliff_.get_zrow(Qubit(it->first)); + auto [pauli_dense, angle] = dense_pauli(paulis, n_qubits_, 1.); + PauliNode_ptr node = std::make_shared( + pauli_dense, (angle == 1.), it->second); + apply_node_at_end(node); + end_measures_.left.erase(it); + } + } else if (arg.type() == UnitType::Bit) { + auto it = end_measures_.right.find(arg.index().at(0)); + if (it != end_measures_.right.end()) { + SpPauliStabiliser paulis = cliff_.get_zrow(Qubit(it->second)); + auto [pauli_dense, angle] = dense_pauli(paulis, n_qubits_, 1.); + PauliNode_ptr node = + std::make_shared(pauli_dense, (angle == 1.), it->first); + apply_node_at_end(node); + end_measures_.right.erase(it); + } + } + } + + std::vector, Expr>> pauli_rots; + switch (type) { + case OpType::Conditional: { + const Conditional& cond = static_cast(*op); + std::vector cond_bits; + unit_vector_t inner_args; + for (unsigned i = 0; i < cond.get_width(); ++i) + cond_bits.push_back(Bit(args.at(i)).index().at(0)); + for (unsigned i = cond.get_width(); i < args.size(); ++i) + inner_args.push_back(args.at(i)); + apply_gate_at_end( + Command(cond.get_op(), inner_args), true, cond_bits, + cond.get_value()); + return; + } + case OpType::Measure: { + end_measures_.insert( + {args.at(0).index().at(0), args.at(1).index().at(0)}); + return; + } + case OpType::Reset: { + SpPauliStabiliser z_paulis = cliff_.get_zrow(qbs[0]); + auto [z_pauli_dense, z_angle] = dense_pauli(z_paulis, n_qubits_, 1.); + SpPauliStabiliser x_paulis = cliff_.get_xrow(qbs[0]); + auto [x_pauli_dense, x_angle] = dense_pauli(x_paulis, n_qubits_, 1.); + PauliNode_ptr node = std::make_shared( + z_pauli_dense, x_pauli_dense, (z_angle == 1.), (x_angle == 1.)); + apply_node_at_end(node); + return; + } + case OpType::Z: { + pauli_rots.push_back({{Pauli::Z}, 1}); + break; + } + case OpType::X: { + pauli_rots.push_back({{Pauli::X}, 1}); + break; + } + case OpType::Y: { + pauli_rots.push_back({{Pauli::Y}, 1}); + break; + } + case OpType::S: { + pauli_rots.push_back({{Pauli::Z}, 0.5}); + break; + } + case OpType::V: { + pauli_rots.push_back({{Pauli::X}, 0.5}); + break; + } + case OpType::Sdg: { + pauli_rots.push_back({{Pauli::Z}, 1.5}); + break; + } + case OpType::Vdg: { + pauli_rots.push_back({{Pauli::X}, 1.5}); + break; + } + case OpType::H: { + pauli_rots.push_back({{Pauli::Y}, 0.5}); + pauli_rots.push_back({{Pauli::X}, 1}); + break; + } + case OpType::CX: + case OpType::CY: + case OpType::CZ: { + Pauli t = (type == OpType::CZ) ? Pauli::Z + : (type == OpType::CX) ? Pauli::X + : Pauli::Y; + pauli_rots.push_back({{Pauli::Z, Pauli::I}, 1.5}); + pauli_rots.push_back({{Pauli::I, t}, 1.5}); + pauli_rots.push_back({{Pauli::Z, t}, 0.5}); + break; + } + case OpType::SWAP: { + pauli_rots.push_back({{Pauli::Z, Pauli::Z}, 0.5}); + pauli_rots.push_back({{Pauli::X, Pauli::X}, 0.5}); + pauli_rots.push_back({{Pauli::Y, Pauli::Y}, 0.5}); + break; + } + case OpType::noop: + case OpType::Phase: { + // ignore global phase + return; + } + case OpType::Rz: { + pauli_rots.push_back({{Pauli::Z}, op->get_params().at(0)}); + break; + } + case OpType::Rx: { + pauli_rots.push_back({{Pauli::X}, op->get_params().at(0)}); + break; + } + case OpType::Ry: { + pauli_rots.push_back({{Pauli::Y}, op->get_params().at(0)}); + break; + } + case OpType::PhasedX: { + Expr alpha = op->get_params().at(0); + Expr beta = op->get_params().at(1); + pauli_rots.push_back({{Pauli::Z}, -beta}); + pauli_rots.push_back({{Pauli::X}, alpha}); + pauli_rots.push_back({{Pauli::Z}, beta}); + break; + } + case OpType::T: { + pauli_rots.push_back({{Pauli::Z}, 0.25}); + break; + } + case OpType::Tdg: { + pauli_rots.push_back({{Pauli::Z}, -0.25}); + break; + } + case OpType::ZZMax: { + pauli_rots.push_back({{Pauli::Z, Pauli::Z}, 0.5}); + break; + } + case OpType::PhaseGadget: + case OpType::ZZPhase: { + Expr angle = op->get_params().at(0); + std::vector paulis(qbs.size(), Pauli::Z); + pauli_rots.push_back({paulis, angle}); + break; + } + case OpType::XXPhase: { + Expr angle = op->get_params().at(0); + pauli_rots.push_back({{Pauli::X, Pauli::X}, angle}); + break; + } + case OpType::YYPhase: { + Expr angle = op->get_params().at(0); + pauli_rots.push_back({{Pauli::Y, Pauli::Y}, angle}); + break; + } + case OpType::PauliExpBox: { + const PauliExpBox& peb = static_cast(*op); + pauli_rots.push_back({peb.get_paulis(), peb.get_phase()}); + break; + } + case OpType::PauliExpPairBox: { + const PauliExpPairBox& peb = static_cast(*op); + auto [paulis1, paulis2] = peb.get_paulis_pair(); + auto [phase1, phase2] = peb.get_phase_pair(); + pauli_rots.push_back({paulis1, phase1}); + pauli_rots.push_back({paulis2, phase2}); + break; + } + case OpType::PauliExpCommutingSetBox: { + const PauliExpCommutingSetBox& peb = + static_cast(*op); + for (const SymPauliTensor& pt : peb.get_pauli_gadgets()) { + pauli_rots.push_back({pt.string, pt.coeff}); + } + break; + } + default: { + if (qbs.empty()) { + // ops with no quantum dependencies + PauliNode_ptr node = std::make_shared(args, op); + apply_node_at_end(node); + return; + } + throw BadOpType("GreedyPauliSimp doesn't support", type); + } + } + apply_paulis_at_end(pauli_rots, qbs, conditional, cond_bits, cond_value); +} + +std::vector GPGraph::vertices_in_order() const { + GPVIndex index = boost::get(boost::vertex_index, graph_); + int i = 0; + BGL_FORALL_VERTICES(v, graph_, GPDAG) { boost::put(index, v, i++); } + std::vector vertices; + boost::topological_sort(graph_, std::back_inserter(vertices)); + std::reverse(vertices.begin(), vertices.end()); + return vertices; +} + +std::tuple< + std::vector>, std::vector, + boost::bimap> +GPGraph::get_sequence() { + std::vector vertices = vertices_in_order(); + auto it = vertices.begin(); + std::vector> interior_nodes; + while (it != vertices.end()) { + const PauliNode_ptr& node = graph_[*it]; + std::vector commuting_set; + commuting_set.push_back(node); + ++it; + while (it != vertices.end()) { + const PauliNode_ptr& u = graph_[*it]; + bool commutes_with_all = true; + for (const PauliNode_ptr& v : commuting_set) { + if (!nodes_commute(u, v)) { + commutes_with_all = false; + break; + } + } + if (!commutes_with_all) break; + commuting_set.push_back(u); + ++it; + } + interior_nodes.push_back(commuting_set); + } + // add clifford + std::vector cliff_nodes = + get_nodes_from_tableau(cliff_, n_qubits_); + return {interior_nodes, cliff_nodes, end_measures_}; +} + +} // namespace GreedyPauliSimp + +} // namespace Transforms + +} // namespace tket diff --git a/tket/src/Transformations/GreedyPauliOps.cpp b/tket/src/Transformations/GreedyPauliOps.cpp new file mode 100644 index 0000000000..3e0af85795 --- /dev/null +++ b/tket/src/Transformations/GreedyPauliOps.cpp @@ -0,0 +1,422 @@ +// Copyright 2019-2024 Cambridge Quantum Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "tket/Circuit/PauliExpBoxes.hpp" +#include "tket/OpType/OpType.hpp" +#include "tket/Transformations/CliffordOptimisation.hpp" +#include "tket/Transformations/GreedyPauliOptimisation.hpp" +#include "tket/Transformations/GreedyPauliOptimisationLookupTables.hpp" + +namespace tket { + +namespace Transforms { + +namespace GreedyPauliSimp { + +static CommuteType get_pauli_pair_commute_type( + const Pauli& p0, const Pauli& p1) { + if (p0 == Pauli::I && p1 == Pauli::I) { + return CommuteType::I; + } + if (p0 == p1 || p0 == Pauli::I || p1 == Pauli::I) { + return CommuteType::C; + } + return CommuteType::A; +} + +// PauliNode abstract class + +PauliNode::~PauliNode() {} + +void PauliNode::update(const OpType& /*sq_cliff*/, const unsigned& /*a*/) { + throw GreedyPauliSimpError("Single qubit Clifford update not implemented."); +} + +void PauliNode::swap(const unsigned& /*a*/, const unsigned& /*b*/) { + throw GreedyPauliSimpError("SWAP update not implemented."); +} + +// SingleNode + +SingleNode::SingleNode(std::vector string, bool sign) + : string_(string), sign_(sign) { + if (string.empty()) { + throw GreedyPauliSimpError("SingleNode cannot have empty strings."); + } + weight_ = + string_.size() - std::count(string_.begin(), string_.end(), Pauli::I); + if (weight_ == 0) { + throw GreedyPauliSimpError( + "SingleNode cannot be constructed with identity strings."); + } +} + +unsigned SingleNode::tqe_cost() const { return weight_ - 1; } + +int SingleNode::tqe_cost_increase(const TQE& tqe) const { + const TQEType& g = tqe.type; + const unsigned& a = tqe.a; + const unsigned& b = tqe.b; + Pauli p0 = string_[a]; + Pauli p1 = string_[b]; + auto [new_p0, new_p1, sign] = TQE_PAULI_MAP.at({g, p0, p1}); + return (p0 == Pauli::I) + (p1 == Pauli::I) - (new_p0 == Pauli::I) - + (new_p1 == Pauli::I); +} + +void SingleNode::update(const TQE& tqe) { + const TQEType& g = tqe.type; + const unsigned& a = tqe.a; + const unsigned& b = tqe.b; + Pauli p0 = string_[a]; + Pauli p1 = string_[b]; + auto [new_p0, new_p1, sign] = TQE_PAULI_MAP.at({g, p0, p1}); + string_[a] = new_p0; + string_[b] = new_p1; + weight_ += (p0 == Pauli::I) + (p1 == Pauli::I) - (new_p0 == Pauli::I) - + (new_p1 == Pauli::I); + if (!sign) { + sign_ = !sign_; + } +} + +std::vector SingleNode::reduction_tqes() const { + std::vector tqes; + // qubits with support + std::vector sqs; + for (unsigned i = 0; i < string_.size(); i++) { + if (string_[i] != Pauli::I) sqs.push_back(i); + } + TKET_ASSERT(!sqs.empty()); + for (unsigned i = 0; i < sqs.size() - 1; i++) { + for (unsigned j = i + 1; j < sqs.size(); j++) { + std::vector tqe_types = + TQE_REDUCTION_MAP.at({string_[sqs[i]], string_[sqs[j]]}); + for (const TQEType& tt : tqe_types) { + tqes.push_back({tt, sqs[i], sqs[j]}); + } + } + } + return tqes; +} + +std::pair SingleNode::first_support() const { + for (unsigned i = 0; i < string_.size(); i++) { + if (string_[i] != Pauli::I) { + return {i, string_[i]}; + } + } + // Should be impossible to reach here + TKET_ASSERT(false); +} + +// ACPairNode + +ACPairNode::ACPairNode( + std::vector z_string, std::vector x_string, bool z_sign, + bool x_sign) + : z_string_(z_string), + x_string_(x_string), + z_sign_(z_sign), + x_sign_(x_sign) { + if (z_string.empty() || x_string.empty()) { + throw GreedyPauliSimpError("ACPairNode cannot have empty strings."); + } + n_commute_entries_ = 0; + n_anti_commute_entries_ = 0; + for (unsigned i = 0; i < z_string_.size(); i++) { + CommuteType commute_type = + get_pauli_pair_commute_type(z_string_[i], x_string_[i]); + commute_type_vec_.push_back(commute_type); + if (commute_type == CommuteType::C) { + n_commute_entries_ += 1; + } + if (commute_type == CommuteType::A) { + n_anti_commute_entries_ += 1; + } + } +} + +unsigned ACPairNode::tqe_cost() const { + // for a node with n A pairs and m C pairs + // it takes (n-1)/2 TQE gates to convert n-1 A + // pairs to C pairs. It then takes n-1+m TQE gates + // to convert all the C pairs to I pairs. + // total TQEs required is 1.5n - 1.5 + m + return static_cast( + 1.5 * (n_anti_commute_entries_ - 1) + n_commute_entries_); +} + +int ACPairNode::tqe_cost_increase(const TQE& tqe) const { + const TQEType& g = tqe.type; + const unsigned& a = tqe.a; + const unsigned& b = tqe.b; + Pauli z_p0 = z_string_[a]; + Pauli z_p1 = z_string_[b]; + Pauli x_p0 = x_string_[a]; + Pauli x_p1 = x_string_[b]; + auto [new_z_p0, new_z_p1, z_sign] = TQE_PAULI_MAP.at({g, z_p0, z_p1}); + auto [new_x_p0, new_x_p1, x_sign] = TQE_PAULI_MAP.at({g, x_p0, x_p1}); + CommuteType new_a_type = get_pauli_pair_commute_type(new_z_p0, new_x_p0); + CommuteType new_b_type = get_pauli_pair_commute_type(new_z_p1, new_x_p1); + unsigned old_anti_commutes = (commute_type_vec_[a] == CommuteType::A) + + (commute_type_vec_[b] == CommuteType::A); + unsigned old_commutes = (commute_type_vec_[a] == CommuteType::C) + + (commute_type_vec_[b] == CommuteType::C); + unsigned new_anti_commutes = + (new_a_type == CommuteType::A) + (new_b_type == CommuteType::A); + unsigned new_commutes = + (new_a_type == CommuteType::C) + (new_b_type == CommuteType::C); + int anti_commute_increase = new_anti_commutes - old_anti_commutes; + int commute_increase = new_commutes - old_commutes; + return static_cast(1.5 * anti_commute_increase + commute_increase); +} + +void ACPairNode::update(const TQE& tqe) { + const TQEType& g = tqe.type; + const unsigned& a = tqe.a; + const unsigned& b = tqe.b; + Pauli z_p0 = z_string_[a]; + Pauli z_p1 = z_string_[b]; + Pauli x_p0 = x_string_[a]; + Pauli x_p1 = x_string_[b]; + auto [new_z_p0, new_z_p1, z_sign] = TQE_PAULI_MAP.at({g, z_p0, z_p1}); + auto [new_x_p0, new_x_p1, x_sign] = TQE_PAULI_MAP.at({g, x_p0, x_p1}); + CommuteType new_a_type = get_pauli_pair_commute_type(new_z_p0, new_x_p0); + CommuteType new_b_type = get_pauli_pair_commute_type(new_z_p1, new_x_p1); + unsigned old_anti_commutes = (commute_type_vec_[a] == CommuteType::A) + + (commute_type_vec_[b] == CommuteType::A); + unsigned old_commutes = (commute_type_vec_[a] == CommuteType::C) + + (commute_type_vec_[b] == CommuteType::C); + unsigned new_anti_commutes = + (new_a_type == CommuteType::A) + (new_b_type == CommuteType::A); + unsigned new_commutes = + (new_a_type == CommuteType::C) + (new_b_type == CommuteType::C); + int anti_commute_increase = new_anti_commutes - old_anti_commutes; + int commute_increase = new_commutes - old_commutes; + n_anti_commute_entries_ += anti_commute_increase; + n_commute_entries_ += commute_increase; + commute_type_vec_[a] = new_a_type; + commute_type_vec_[b] = new_b_type; + z_string_[a] = new_z_p0; + z_string_[b] = new_z_p1; + x_string_[a] = new_x_p0; + x_string_[b] = new_x_p1; + if (!z_sign) { + z_sign_ = !z_sign_; + } + if (!x_sign) { + x_sign_ = !x_sign_; + } +} + +void ACPairNode::update(const OpType& sq_cliff, const unsigned& a) { + auto [new_z_p, z_sign] = SQ_CLIFF_MAP.at({sq_cliff, z_string_[a]}); + auto [new_x_p, x_sign] = SQ_CLIFF_MAP.at({sq_cliff, x_string_[a]}); + z_string_[a] = new_z_p; + x_string_[a] = new_x_p; + if (!z_sign) { + z_sign_ = !z_sign_; + } + if (!x_sign) { + x_sign_ = !x_sign_; + } +} + +void ACPairNode::swap(const unsigned& a, const unsigned& b) { + std::swap(z_string_[a], z_string_[b]); + std::swap(x_string_[a], x_string_[b]); + std::swap(commute_type_vec_[a], commute_type_vec_[b]); +} + +std::vector ACPairNode::reduction_tqes() const { + std::vector tqes; + // qubits with support + std::vector sqs; + for (unsigned i = 0; i < commute_type_vec_.size(); i++) { + if (commute_type_vec_[i] != CommuteType::I) sqs.push_back(i); + } + TKET_ASSERT(!sqs.empty()); + for (unsigned i = 0; i < sqs.size() - 1; i++) { + for (unsigned j = i + 1; j < sqs.size(); j++) { + std::vector tqe_types; + unsigned a = sqs[i]; + unsigned b = sqs[j]; + CommuteType ctype0 = commute_type_vec_[a]; + CommuteType ctype1 = commute_type_vec_[b]; + if (ctype0 == CommuteType::A) { + if (ctype1 == CommuteType::A) { + // TQEs that transform a AA pair to CC + tqe_types = AA_TO_CC_MAP.at( + {z_string_[a], z_string_[b], x_string_[a], x_string_[b]}); + } else { + // TQEs that transform a AC pair to AI + tqe_types = AC_TO_AI_MAP.at( + {z_string_[a], z_string_[b], x_string_[a], x_string_[b]}); + } + } else { + if (ctype1 == CommuteType::A) { + // TQEs that transform a CA pair to a IA + tqe_types = AC_TO_AI_MAP.at( + {z_string_[b], z_string_[a], x_string_[b], x_string_[a]}); + // flip qubits + a = sqs[j]; + b = sqs[i]; + } else { + // TQEs that transform a CC pair to CI or IC, not always + // possible + tqe_types = CC_TO_IC_OR_CI_MAP.at( + {z_string_[a], z_string_[b], x_string_[a], x_string_[b]}); + } + } + for (const TQEType& tt : tqe_types) { + tqes.push_back({tt, a, b}); + } + } + } + return tqes; +} + +std::tuple ACPairNode::first_support() const { + for (unsigned i = 0; i < commute_type_vec_.size(); i++) { + if (commute_type_vec_[i] != CommuteType::I) { + return {i, z_string_[i], x_string_[i]}; + } + } + // Should be impossible to reach here + TKET_ASSERT(false); +} + +// PauliRotation +PauliRotation::PauliRotation(std::vector string, bool sign, Expr theta) + : SingleNode(string, sign), theta_(theta) {} + +CommuteInfo PauliRotation::get_commute_info() const { return {{string_}, {}}; } + +ConditionalBlock::ConditionalBlock( + std::vector, bool, Expr>> rotations, + std::vector cond_bits, unsigned cond_value) + : rotations_(rotations), cond_bits_(cond_bits), cond_value_(cond_value) { + total_weight_ = 0; + for (const std::tuple, bool, Expr>& rot : rotations_) { + total_weight_ += + std::get<0>(rot).size() - + std::count(std::get<0>(rot).begin(), std::get<0>(rot).end(), Pauli::I); + } +} + +unsigned ConditionalBlock::tqe_cost() const { return total_weight_ - 1; } + +int ConditionalBlock::tqe_cost_increase(const TQE& tqe) const { + int total_increase = 0; + for (const std::tuple, bool, Expr>& rot : rotations_) { + const TQEType& g = tqe.type; + const unsigned& a = tqe.a; + const unsigned& b = tqe.b; + Pauli p0 = std::get<0>(rot)[a]; + Pauli p1 = std::get<0>(rot)[b]; + auto [new_p0, new_p1, sign] = TQE_PAULI_MAP.at({g, p0, p1}); + total_increase += (p0 == Pauli::I) + (p1 == Pauli::I) - + (new_p0 == Pauli::I) - (new_p1 == Pauli::I); + } + return total_increase; +} + +void ConditionalBlock::update(const TQE& tqe) { + for (std::tuple, bool, Expr>& rot : rotations_) { + const TQEType& g = tqe.type; + const unsigned& a = tqe.a; + const unsigned& b = tqe.b; + Pauli p0 = std::get<0>(rot)[a]; + Pauli p1 = std::get<0>(rot)[b]; + auto [new_p0, new_p1, sign] = TQE_PAULI_MAP.at({g, p0, p1}); + std::get<0>(rot)[a] = new_p0; + std::get<0>(rot)[b] = new_p1; + total_weight_ += (p0 == Pauli::I) + (p1 == Pauli::I) - + (new_p0 == Pauli::I) - (new_p1 == Pauli::I); + if (!sign) { + std::get<1>(rot) = !std::get<1>(rot); + } + } +} + +CommuteInfo ConditionalBlock::get_commute_info() const { + std::vector> bits_info; + for (unsigned b : cond_bits_) { + bits_info.push_back({Bit(b), BitType::READ}); + } + std::vector> strings; + for (const std::tuple, bool, Expr>& rot : rotations_) { + strings.push_back(std::get<0>(rot)); + } + return {strings, bits_info}; +} + +void ConditionalBlock::append(const ConditionalBlock& other) { + for (const auto& rot : other.rotations()) { + rotations_.push_back(rot); + total_weight_ += + std::get<0>(rot).size() - + std::count(std::get<0>(rot).begin(), std::get<0>(rot).end(), Pauli::I); + } +} + +// PauliPropagation +PauliPropagation::PauliPropagation( + std::vector z_string, std::vector x_string, bool z_sign, + bool x_sign, unsigned qubit_index) + : ACPairNode(z_string, x_string, z_sign, x_sign), + qubit_index_(qubit_index) {} + +CommuteInfo PauliPropagation::get_commute_info() const { + return {{z_string_, x_string_}, {}}; +} + +// ClassicalNode +ClassicalNode::ClassicalNode(std::vector args, Op_ptr op) + : args_(args), op_(op) {} + +CommuteInfo ClassicalNode::get_commute_info() const { + std::vector> bits_info; + for (const UnitID& b : args_) { + bits_info.push_back({b, BitType::WRITE}); + } + return {{}, bits_info}; +} + +// MidMeasure +MidMeasure::MidMeasure(std::vector string, bool sign, unsigned bit) + : SingleNode(string, sign), bit_(bit) {} + +CommuteInfo MidMeasure::get_commute_info() const { + return {{string_}, {{Bit(bit_), BitType::WRITE}}}; +} + +// Reset +Reset::Reset( + std::vector z_string, std::vector x_string, bool z_sign, + bool x_sign) + : ACPairNode(z_string, x_string, z_sign, x_sign) {} + +CommuteInfo Reset::get_commute_info() const { + return {{z_string_, x_string_}, {}}; +} + +} // namespace GreedyPauliSimp + +} // namespace Transforms + +} // namespace tket diff --git a/tket/src/Transformations/GreedyPauliOptimisation.cpp b/tket/src/Transformations/GreedyPauliOptimisation.cpp index 9515ddbbc5..e052102656 100644 --- a/tket/src/Transformations/GreedyPauliOptimisation.cpp +++ b/tket/src/Transformations/GreedyPauliOptimisation.cpp @@ -15,12 +15,10 @@ #include "tket/Transformations/GreedyPauliOptimisation.hpp" #include +#include #include "tket/Circuit/PauliExpBoxes.hpp" -#include "tket/Converters/Converters.hpp" #include "tket/OpType/OpType.hpp" -#include "tket/PauliGraph/PauliGraph.hpp" -#include "tket/Transformations/CliffordOptimisation.hpp" #include "tket/Transformations/GreedyPauliOptimisationLookupTables.hpp" #include "tket/Transformations/Transform.hpp" @@ -30,8 +28,61 @@ namespace Transforms { namespace GreedyPauliSimp { +static TQE sample_random_tqe(const std::vector& vec, unsigned seed) { + std::mt19937 rng(seed); + std::uniform_int_distribution dist(0, vec.size() - 1); + size_t random_index = dist(rng); + auto it = vec.begin(); + std::advance(it, random_index); + return *it; +} + +static std::vector sample_tqes( + const std::set& tqes, size_t k, unsigned seed) { + // https://stackoverflow.com/a/59090754 + size_t unsampled_sz = tqes.size(); + auto first = std::begin(tqes); + std::vector vec; + std::mt19937 rng(seed); + vec.reserve(std::min(k, unsampled_sz)); + for (k = std::min(k, unsampled_sz); k != 0; ++first) { + auto r = std::uniform_int_distribution(0, --unsampled_sz)(rng); + if (r < k) { + vec.push_back(*first); + --k; + } + } + return vec; +} + +static void apply_rot2q_to_circ(const Rotation2Q& rot, Circuit& circ) { + if (rot.p_a == Pauli::X) { + circ.add_op(OpType::H, {rot.a}); + } else if (rot.p_a == Pauli::Y) { + circ.add_op(OpType::V, {rot.a}); + } + if (rot.p_b == Pauli::X) { + circ.add_op(OpType::H, {rot.b}); + } else if (rot.p_b == Pauli::Y) { + circ.add_op(OpType::V, {rot.b}); + } + circ.add_op(OpType::ZZPhase, rot.angle, {rot.a, rot.b}); + if (rot.p_a == Pauli::X) { + circ.add_op(OpType::H, {rot.a}); + } else if (rot.p_a == Pauli::Y) { + circ.add_op(OpType::Vdg, {rot.a}); + } + if (rot.p_b == Pauli::X) { + circ.add_op(OpType::H, {rot.b}); + } else if (rot.p_b == Pauli::Y) { + circ.add_op(OpType::Vdg, {rot.b}); + } +} + static void apply_tqe_to_circ(const TQE& tqe, Circuit& circ) { - auto [gate_type, a, b] = tqe; + const TQEType& gate_type = tqe.type; + const unsigned& a = tqe.a; + const unsigned& b = tqe.b; switch (gate_type) { case TQEType::XX: circ.add_op(OpType::H, {a}); @@ -71,233 +122,16 @@ static void apply_tqe_to_circ(const TQE& tqe, Circuit& circ) { } } -static void apply_tqe_to_tableau(const TQE& tqe, UnitaryRevTableau& tab) { - auto [gate_type, a_int, b_int] = tqe; - Qubit a(a_int); - Qubit b(b_int); - switch (gate_type) { - case TQEType::XX: - tab.apply_gate_at_end(OpType::H, {a}); - tab.apply_gate_at_end(OpType::CX, {a, b}); - tab.apply_gate_at_end(OpType::H, {a}); - break; - case TQEType::XY: - tab.apply_gate_at_end(OpType::H, {a}); - tab.apply_gate_at_end(OpType::CY, {a, b}); - tab.apply_gate_at_end(OpType::H, {a}); - break; - case TQEType::XZ: - tab.apply_gate_at_end(OpType::CX, {b, a}); - break; - case TQEType::YX: - tab.apply_gate_at_end(OpType::H, {b}); - tab.apply_gate_at_end(OpType::CY, {b, a}); - tab.apply_gate_at_end(OpType::H, {b}); - break; - case TQEType::YY: - tab.apply_gate_at_end(OpType::V, {a}); - tab.apply_gate_at_end(OpType::CY, {a, b}); - tab.apply_gate_at_end(OpType::Vdg, {a}); - break; - case TQEType::YZ: - tab.apply_gate_at_end(OpType::CY, {b, a}); - break; - case TQEType::ZX: - tab.apply_gate_at_end(OpType::CX, {a, b}); - break; - case TQEType::ZY: - tab.apply_gate_at_end(OpType::CY, {a, b}); - break; - case TQEType::ZZ: - tab.apply_gate_at_end(OpType::CZ, {a, b}); - break; - } -} - -PauliExpNode::PauliExpNode(std::vector support_vec, Expr theta) - : support_vec_(support_vec), theta_(theta) { - tqe_cost_ = support_vec_.size() - - std::count(support_vec_.begin(), support_vec_.end(), 0) - 1; -} - -int PauliExpNode::tqe_cost_increase(const TQE& tqe) const { - unsigned supp0 = support_vec_[std::get<1>(tqe)]; - unsigned supp1 = support_vec_[std::get<2>(tqe)]; - unsigned new_supp0, new_supp1; - std::tie(new_supp0, new_supp1) = - SINGLET_PAIR_TRANSFORMATION_MAP.at({std::get<0>(tqe), supp0, supp1}); - return (new_supp0 > 0) + (new_supp1 > 0) - (supp0 > 0) - (supp1 > 0); -} - -void PauliExpNode::update(const TQE& tqe) { - unsigned a = std::get<1>(tqe); - unsigned b = std::get<2>(tqe); - unsigned supp0 = support_vec_[a]; - unsigned supp1 = support_vec_[b]; - unsigned new_supp0, new_supp1; - std::tie(new_supp0, new_supp1) = - SINGLET_PAIR_TRANSFORMATION_MAP.at({std::get<0>(tqe), supp0, supp1}); - support_vec_[a] = new_supp0; - support_vec_[b] = new_supp1; - tqe_cost_ += (new_supp0 > 0) + (new_supp1 > 0) - (supp0 > 0) - (supp1 > 0); -} - -std::vector PauliExpNode::reduction_tqes() const { - std::vector tqes; - // qubits with support - std::vector sqs; - for (unsigned i = 0; i < support_vec_.size(); i++) { - if (support_vec_[i] > 0) sqs.push_back(i); - } - for (unsigned i = 0; i < sqs.size() - 1; i++) { - for (unsigned j = i + 1; j < sqs.size(); j++) { - std::vector tqe_types = SINGLET_PAIR_REDUCTION_TQES.at( - {support_vec_[sqs[i]], support_vec_[sqs[j]]}); - for (const TQEType& tt : tqe_types) { - tqes.push_back({tt, sqs[i], sqs[j]}); - } - } - } - return tqes; -} - -std::pair PauliExpNode::first_support() const { - for (unsigned i = 0; i < support_vec_.size(); i++) { - if (support_vec_[i] > 0) { - return {i, support_vec_[i]}; - } - } - // Should be impossible to reach here - TKET_ASSERT(false); -} - -TableauRowNode::TableauRowNode(std::vector support_vec) - : support_vec_(support_vec) { - n_weaks_ = 0; - n_strongs_ = 0; - for (const unsigned& supp : support_vec_) { - SupportType st = FACTOR_WEAKNESS_MAP.at(supp); - if (st == SupportType::Strong) { - n_strongs_++; - } else if (st == SupportType::Weak) { - n_weaks_++; - } - } - tqe_cost_ = static_cast(1.5 * (n_strongs_ - 1) + n_weaks_); -} - -int TableauRowNode::tqe_cost_increase(const TQE& tqe) const { - unsigned supp0 = support_vec_[std::get<1>(tqe)]; - unsigned supp1 = support_vec_[std::get<2>(tqe)]; - unsigned new_supp0, new_supp1; - std::tie(new_supp0, new_supp1) = - FACTOR_PAIR_TRANSFORMATION_MAP.at({std::get<0>(tqe), supp0, supp1}); - SupportType st_supp0 = FACTOR_WEAKNESS_MAP.at(supp0); - SupportType st_supp1 = FACTOR_WEAKNESS_MAP.at(supp1); - SupportType st_new_supp0 = FACTOR_WEAKNESS_MAP.at(new_supp0); - SupportType st_new_supp1 = FACTOR_WEAKNESS_MAP.at(new_supp1); - unsigned old_strongs = - (st_supp0 == SupportType::Strong) + (st_supp1 == SupportType::Strong); - unsigned old_weaks = - (st_supp0 == SupportType::Weak) + (st_supp1 == SupportType::Weak); - unsigned new_strongs = (st_new_supp0 == SupportType::Strong) + - (st_new_supp1 == SupportType::Strong); - unsigned new_weaks = - (st_new_supp0 == SupportType::Weak) + (st_new_supp1 == SupportType::Weak); - int strong_increase = new_strongs - old_strongs; - int weak_increase = new_weaks - old_weaks; - return static_cast(1.5 * strong_increase + weak_increase); -} - -void TableauRowNode::update(const TQE& tqe) { - unsigned a = std::get<1>(tqe); - unsigned b = std::get<2>(tqe); - unsigned supp0 = support_vec_[a]; - unsigned supp1 = support_vec_[b]; - unsigned new_supp0, new_supp1; - std::tie(new_supp0, new_supp1) = - FACTOR_PAIR_TRANSFORMATION_MAP.at({std::get<0>(tqe), supp0, supp1}); - support_vec_[a] = new_supp0; - support_vec_[b] = new_supp1; - SupportType st_supp0 = FACTOR_WEAKNESS_MAP.at(supp0); - SupportType st_supp1 = FACTOR_WEAKNESS_MAP.at(supp1); - SupportType st_new_supp0 = FACTOR_WEAKNESS_MAP.at(new_supp0); - SupportType st_new_supp1 = FACTOR_WEAKNESS_MAP.at(new_supp1); - unsigned old_strongs = - (st_supp0 == SupportType::Strong) + (st_supp1 == SupportType::Strong); - unsigned old_weaks = - (st_supp0 == SupportType::Weak) + (st_supp1 == SupportType::Weak); - unsigned new_strongs = (st_new_supp0 == SupportType::Strong) + - (st_new_supp1 == SupportType::Strong); - unsigned new_weaks = - (st_new_supp0 == SupportType::Weak) + (st_new_supp1 == SupportType::Weak); - n_strongs_ += new_strongs - old_strongs; - n_weaks_ += new_weaks - old_weaks; - tqe_cost_ = static_cast(1.5 * (n_strongs_ - 1) + n_weaks_); -} - -std::vector TableauRowNode::reduction_tqes() const { - std::vector tqes; - // qubits with support - std::vector sqs; - for (unsigned i = 0; i < support_vec_.size(); i++) { - if (support_vec_[i] > 0) sqs.push_back(i); - } - for (unsigned i = 0; i < sqs.size() - 1; i++) { - for (unsigned j = i + 1; j < sqs.size(); j++) { - std::vector tqe_types; - unsigned a = sqs[i]; - unsigned b = sqs[j]; - unsigned supp0 = support_vec_[a]; - unsigned supp1 = support_vec_[b]; - SupportType st_supp0 = FACTOR_WEAKNESS_MAP.at(supp0); - SupportType st_supp1 = FACTOR_WEAKNESS_MAP.at(supp1); - if (st_supp0 == SupportType::Strong) { - if (st_supp1 == SupportType::Strong) { - // TQEs that transform a SS pair to WW - tqe_types = FACTOR_PAIR_SS_TO_WW_TQES.at({supp0, supp1}); - } else { - // TQEs that transform a SW pair to a single strong - tqe_types = FACTOR_PAIR_SW_TO_SN_TQES.at({supp0, supp1}); - } - } else { - if (st_supp1 == SupportType::Strong) { - // TQEs that transform a WS pair to a single strong - tqe_types = FACTOR_PAIR_SW_TO_SN_TQES.at({supp1, supp0}); - // flip qubits - a = sqs[j]; - b = sqs[i]; - } else { - // TQEs that transform a WW pair to a single weak, not always - // possible - tqe_types = FACTOR_PAIR_WW_TO_WN_OR_NW_TQES.at({supp0, supp1}); - } - } - for (const TQEType& tt : tqe_types) { - tqes.push_back({tt, a, b}); - } - } - } - return tqes; -} - -std::pair TableauRowNode::first_support() const { - for (unsigned i = 0; i < support_vec_.size(); i++) { - if (support_vec_[i] > 0) { - return {i, support_vec_[i]}; - } - } - // Should be impossible to reach here - TKET_ASSERT(false); -} - // return the sum of the cost increases on remaining tableau nodes static double default_tableau_tqe_cost( - const std::vector& rows, - const std::vector& remaining_indices, const TQE& tqe) { + const std::vector& rows, + const std::vector& remaining_indices, const TQE& tqe, + const unsigned& max_lookahead) { double cost = 0; + unsigned count = 0; for (const unsigned& index : remaining_indices) { - cost += rows[index].tqe_cost_increase(tqe); + cost += rows[index]->tqe_cost_increase(tqe); + if (++count >= max_lookahead) break; } return cost; } @@ -306,28 +140,36 @@ static double default_tableau_tqe_cost( // we discount the weight after each set static double default_pauliexp_tqe_cost( const double discount_rate, - const std::vector>& rotation_sets, - const std::vector& rows, const TQE& tqe) { + const std::vector>& rotation_sets, + const std::vector& rows, const TQE& tqe, + const unsigned& max_lookahead) { double discount = 1 / (1 + discount_rate); double weight = 1; double exp_cost = 0; double tab_cost = 0; - for (const std::vector& rotation_set : rotation_sets) { - for (const PauliExpNode& node : rotation_set) { - exp_cost += weight * node.tqe_cost_increase(tqe); + unsigned count = 0; + for (const std::vector& rotation_set : rotation_sets) { + for (const PauliNode_ptr& node : rotation_set) { + exp_cost += weight * node->tqe_cost_increase(tqe); + if (++count >= max_lookahead) break; } + if (count >= max_lookahead) break; weight *= discount; } - for (const TableauRowNode& node : rows) { - tab_cost += weight * node.tqe_cost_increase(tqe); + for (const PauliNode_ptr& node : rows) { + tab_cost += weight * node->tqe_cost_increase(tqe); + if (++count >= max_lookahead) break; } return exp_cost + tab_cost; } -// given a map from TQE to a vector of costs, select the one with the minimum -// weighted sum of minmax-normalised costs -static TQE minmax_selection( +// given a map from TQE to a vector of costs, and an optional map +// specifying the costs for implementing some 2q rotations directly +// as ZZPhase gates. Select the TQEs and 2q rotations with the minimum +// weighted sum of minmax-normalised costs. +static std::pair, std::vector> minmax_selection( const std::map>& tqe_candidates_cost, + const std::map>& rot2q_gates_cost, const std::vector& weights) { TKET_ASSERT(tqe_candidates_cost.size() > 0); size_t n_costs = tqe_candidates_cost.begin()->second.size(); @@ -346,6 +188,17 @@ static TQE minmax_selection( } } } + for (const auto& pair : tqe_candidates_cost) { + TKET_ASSERT(pair.second.size() == n_costs); + for (unsigned cost_index = 0; cost_index < n_costs; cost_index++) { + if (pair.second[cost_index] < mins[cost_index]) { + mins[cost_index] = pair.second[cost_index]; + } + if (pair.second[cost_index] > maxs[cost_index]) { + maxs[cost_index] = pair.second[cost_index]; + } + } + } // valid_indices stores the indices of the costs where min!=max std::vector valid_indices; for (unsigned cost_index = 0; cost_index < n_costs; cost_index++) { @@ -355,26 +208,50 @@ static TQE minmax_selection( } // if all have the same cost, return the first one if (valid_indices.size() == 0) { - TQE min_tqe = tqe_candidates_cost.begin()->first; - return min_tqe; + std::vector rot2qs; + rot2qs.reserve(rot2q_gates_cost.size()); + std::transform( + rot2q_gates_cost.begin(), rot2q_gates_cost.end(), + std::back_inserter(rot2qs), + [](const auto& pair) { return pair.first; }); + std::vector selected_tqes; + selected_tqes.reserve(tqe_candidates_cost.size()); + std::transform( + tqe_candidates_cost.begin(), tqe_candidates_cost.end(), + std::back_inserter(selected_tqes), + [](const auto& pair) { return pair.first; }); + return {selected_tqes, rot2qs}; } // if only one cost variable, no need to normalise if (valid_indices.size() == 1) { auto it = tqe_candidates_cost.begin(); double min_cost = it->second[valid_indices[0]]; - TQE min_tqe = it->first; + std::vector min_tqes = {it->first}; for (; it != tqe_candidates_cost.end(); it++) { if (it->second[valid_indices[0]] < min_cost) { - min_tqe = it->first; + min_tqes = {it->first}; min_cost = it->second[valid_indices[0]]; + } else if (it->second[valid_indices[0]] == min_cost) { + min_tqes.push_back(it->first); + } + } + std::vector min_rot2qs; + for (auto it2 = rot2q_gates_cost.begin(); it2 != rot2q_gates_cost.end(); + it2++) { + if (it2->second[valid_indices[0]] < min_cost) { + min_tqes.clear(); + min_cost = it2->second[valid_indices[0]]; + min_rot2qs = {it2->first}; + } else if (it2->second[valid_indices[0]] == min_cost) { + min_rot2qs.push_back(it2->first); } } - return min_tqe; + return {min_tqes, min_rot2qs}; } // find the tqe with the minimum normalised cost auto it = tqe_candidates_cost.begin(); double min_cost = 0; - TQE min_tqe = it->first; + std::vector min_tqes = {it->first}; // initialise min_cost for (const auto& cost_index : valid_indices) { min_cost += weights[cost_index] * @@ -392,22 +269,29 @@ static TQE minmax_selection( } if (cost < min_cost) { min_cost = cost; - min_tqe = it->first; + min_tqes = {it->first}; + } else if (cost == min_cost) { + min_tqes.push_back(it->first); } } - return min_tqe; -} - -static TQE select_pauliexp_tqe( - const std::map>& tqe_candidates_cost, - double depth_weight) { - return minmax_selection(tqe_candidates_cost, {1, depth_weight}); -} - -static TQE select_tableau_tqe( - const std::map>& tqe_candidates_cost, - double depth_weight) { - return minmax_selection(tqe_candidates_cost, {1, depth_weight}); + std::vector min_rot2qs; + for (auto it2 = rot2q_gates_cost.begin(); it2 != rot2q_gates_cost.end(); + it2++) { + double cost = 0; + for (const auto& cost_index : valid_indices) { + cost += weights[cost_index] * + (it2->second[cost_index] - mins[cost_index]) / + (maxs[cost_index] - mins[cost_index]); + } + if (cost < min_cost) { + min_cost = cost; + min_tqes.clear(); + min_rot2qs = {it2->first}; + } else if (cost == min_cost) { + min_rot2qs.push_back(it2->first); + } + } + return {min_tqes, min_rot2qs}; } // simple struct that tracks the depth on each qubit @@ -436,103 +320,25 @@ struct DepthTracker { }; /** - * @brief Given a tableau that is identity up to local Cliffords, qubit - * permutation, and signs, transform it to exact identity and adding gates to - * a circuit - */ -static void tableau_cleanup( - std::vector& rows, UnitaryRevTableau& tab, Circuit& circ) { - // apply local Cliffords - for (const TableauRowNode& node : rows) { - unsigned q_index, supp; - std::tie(q_index, supp) = node.first_support(); - Qubit q(q_index); - std::vector local_cliffords = - FACTOR_STRONG_TO_LOCALS.at(supp); - for (const LocalCliffordType& lc : local_cliffords) { - switch (lc) { - case LocalCliffordType::H: - tab.apply_gate_at_end(OpType::H, {q}); - circ.add_op(OpType::H, {q}); - break; - case LocalCliffordType::S: - tab.apply_gate_at_end(OpType::S, {q}); - circ.add_op(OpType::S, {q}); - break; - case LocalCliffordType::V: - tab.apply_gate_at_end(OpType::V, {q}); - circ.add_op(OpType::V, {q}); - break; - } - } - } - // remove signs - for (const Qubit& q : circ.all_qubits()) { - if (cast_coeff(tab.get_xrow(q).coeff) != 1.) { - tab.apply_gate_at_end(OpType::Z, {q}); - circ.add_op(OpType::Z, {q}); - } - if (cast_coeff(tab.get_zrow(q).coeff) != 1.) { - tab.apply_gate_at_end(OpType::X, {q}); - circ.add_op(OpType::X, {q}); - } - } - // remove permutations - // 1. find perm - unsigned n_qubits = circ.n_qubits(); - std::vector perm(n_qubits); - for (unsigned i = 0; i < n_qubits; i++) { - QubitPauliMap z_row_string = tab.get_zrow(Qubit(i)).string; - for (auto it = z_row_string.begin(); it != z_row_string.end(); it++) { - if (it->second == Pauli::Z) { - perm[it->first.index()[0]] = i; - break; - } - } - } - // 2. traverse transpositions - std::unordered_set done; - for (unsigned k = 0; k < n_qubits; k++) { - if (done.find(k) != done.end()) { - continue; - } - unsigned head = k; - unsigned current = k; - unsigned next = perm[k]; - while (true) { - if (next == head) { - done.insert(current); - break; - } - // the SWAP gates will be later converted to wire swaps - tab.apply_gate_at_end(OpType::SWAP, {Qubit(current), Qubit(next)}); - circ.add_op(OpType::SWAP, {current, next}); - done.insert(current); - current = next; - next = perm[current]; - } - } -} - -/** - * @brief Synthesise a vector of TableauRowNode + * @brief Synthesise a vector of PauliPropagation */ static void tableau_row_nodes_synthesis( - std::vector& rows, UnitaryRevTableau& tab, Circuit& circ, - double depth_weight, DepthTracker& depth_tracker) { + std::vector& rows, Circuit& circ, + DepthTracker& depth_tracker, double depth_weight, unsigned max_lookahead, + unsigned max_tqe_candidates, unsigned seed) { // only consider nodes with a non-zero cost std::vector remaining_indices; for (unsigned i = 0; i < rows.size(); i++) { - if (rows[i].tqe_cost() > 0) { + if (rows[i]->tqe_cost() > 0) { remaining_indices.push_back(i); } } while (remaining_indices.size() != 0) { // get nodes with min cost std::vector min_nodes_indices = {remaining_indices[0]}; - unsigned min_cost = rows[remaining_indices[0]].tqe_cost(); + unsigned min_cost = rows[remaining_indices[0]]->tqe_cost(); for (unsigned i = 1; i < remaining_indices.size(); i++) { - unsigned node_cost = rows[remaining_indices[i]].tqe_cost(); + unsigned node_cost = rows[remaining_indices[i]]->tqe_cost(); if (node_cost == min_cost) { min_nodes_indices.push_back(remaining_indices[i]); } else if (node_cost < min_cost) { @@ -545,140 +351,271 @@ static void tableau_row_nodes_synthesis( std::set tqe_candidates; TKET_ASSERT(min_nodes_indices.size() > 0); for (const unsigned& index : min_nodes_indices) { - std::vector node_reducing_tqes = rows[index].reduction_tqes(); + std::vector node_reducing_tqes = rows[index]->reduction_tqes(); tqe_candidates.insert( node_reducing_tqes.begin(), node_reducing_tqes.end()); } + // sample + std::vector sampled_tqes = + sample_tqes(tqe_candidates, max_tqe_candidates, seed); // for each tqe we compute a vector of cost factors which will // be combined to make the final decision. // we currently only consider tqe_cost and gate_depth. std::map> tqe_candidates_cost; - for (const TQE& tqe : tqe_candidates) { + for (const TQE& tqe : sampled_tqes) { tqe_candidates_cost.insert( {tqe, - {default_tableau_tqe_cost(rows, remaining_indices, tqe), - static_cast(depth_tracker.gate_depth( - std::get<1>(tqe), std::get<2>(tqe)))}}); + {default_tableau_tqe_cost( + rows, remaining_indices, tqe, max_lookahead), + static_cast(depth_tracker.gate_depth(tqe.a, tqe.b))}}); } TKET_ASSERT(tqe_candidates_cost.size() > 0); // select the best one - TQE selected_tqe = select_tableau_tqe(tqe_candidates_cost, depth_weight); + auto [min_tqes, _] = + minmax_selection(tqe_candidates_cost, {}, {1, depth_weight}); + TQE selected_tqe = sample_random_tqe(min_tqes, seed); // apply TQE apply_tqe_to_circ(selected_tqe, circ); - apply_tqe_to_tableau(selected_tqe, tab); // update depth tracker - depth_tracker.add_2q_gate( - std::get<1>(selected_tqe), std::get<2>(selected_tqe)); + depth_tracker.add_2q_gate(selected_tqe.a, selected_tqe.b); // remove finished nodes for (unsigned i = remaining_indices.size(); i-- > 0;) { unsigned node_index = remaining_indices[i]; - rows[node_index].update(selected_tqe); - if (rows[node_index].tqe_cost() == 0) { + rows[node_index]->update(selected_tqe); + if (rows[node_index]->tqe_cost() == 0) { remaining_indices.erase(remaining_indices.begin() + i); } } } - tableau_cleanup(rows, tab, circ); + // apply local Cliffords + for (PauliNode_ptr& node_ptr : rows) { + PauliPropagation& node = dynamic_cast(*node_ptr); + auto [q_index, supp_z, supp_x] = node.first_support(); + // transform supp_z,supp_x to Z,X + std::vector optype_list = AA_TO_ZX.at({supp_z, supp_x}); + for (auto it = optype_list.rbegin(); it != optype_list.rend(); ++it) { + circ.add_op(SQ_CLIFF_DAGGER.at(*it), {q_index}); + node.update(*it, q_index); + } + // remove signs + if (!node.z_sign()) { + circ.add_op(OpType::X, {q_index}); + node.update(OpType::X, q_index); + } + if (!node.x_sign()) { + circ.add_op(OpType::Z, {q_index}); + node.update(OpType::Z, q_index); + } + if (q_index != node.qubit_index()) { + circ.add_op(OpType::SWAP, {q_index, node.qubit_index()}); + for (PauliNode_ptr& node_ptr2 : rows) { + node_ptr2->swap(q_index, node.qubit_index()); + } + } + } } /** - * @brief Given a vector of sets of PauliExpNode, implement any node in the + * @brief Given a vector of sets of PauliNodes, implement any node in the * first set where the tqe_cost is zero. Remove implemented nodes and the first * set if empty. * * @param rotation_sets - * @param tab * @param circ * @return true if the first set is now empty and removed * @return false */ -static bool consume_available_rotations( - std::vector>& rotation_sets, - UnitaryRevTableau& tab, Circuit& circ, DepthTracker& depth_tracker) { - std::vector bin; - if (rotation_sets.size() == 0) { - return false; +static void consume_nodes( + std::vector>& rotation_sets, Circuit& circ, + DepthTracker& depth_tracker, double discount_rate, double depth_weight) { + if (rotation_sets.empty()) { + return; } - std::vector& first_set = rotation_sets[0]; - for (unsigned i = 0; i < first_set.size(); i++) { - PauliExpNode& node = first_set[i]; - if (node.tqe_cost() > 0) continue; - unsigned q_index, supp; - std::tie(q_index, supp) = node.first_support(); - Qubit q(q_index); - depth_tracker.add_1q_gate(q_index); - switch (supp) { - case 3: { - // we apply S gate only to the frame, then check the sign, then Sdg - // if + apply f.Sdg; circ.Ry(-a) - // if - apply f.Sdg; circ.Ry(a) - tab.apply_gate_at_end(OpType::S, {q}); - Complex x_coeff = - cast_coeff(tab.get_xrow(q).coeff); - tab.apply_gate_at_end(OpType::Sdg, {q}); - if (x_coeff == 1.) { - circ.add_op(OpType::Ry, -node.theta(), {q}); - } else { - circ.add_op(OpType::Ry, node.theta(), {q}); + while (true) { + std::vector& first_set = rotation_sets[0]; + for (unsigned i = first_set.size(); i-- > 0;) { + PauliNode_ptr& node_ptr = first_set[i]; + switch (node_ptr->get_type()) { + case PauliNodeType::Reset: { + if (node_ptr->tqe_cost() > 0) continue; + Reset& node = dynamic_cast(*node_ptr); + auto [q_index, supp_z, supp_x] = node.first_support(); + // conjugate the pair to +Z/X + std::vector optype_list = AA_TO_ZX.at({supp_z, supp_x}); + for (auto it = optype_list.begin(); it != optype_list.end(); ++it) { + circ.add_op(*it, {q_index}); + } + if (!node.z_sign()) { + circ.add_op(OpType::X, {q_index}); + } + if (!node.x_sign()) { + circ.add_op(OpType::Z, {q_index}); + } + circ.add_op(OpType::Reset, {q_index}); + if (!node.z_sign()) { + circ.add_op(OpType::X, {q_index}); + } + if (!node.x_sign()) { + circ.add_op(OpType::Z, {q_index}); + } + for (auto it = optype_list.rbegin(); it != optype_list.rend(); ++it) { + circ.add_op(SQ_CLIFF_DAGGER.at(*it), {q_index}); + } + first_set.erase(first_set.begin() + i); + break; } - break; - } - case 1: { - Complex z_coeff = - cast_coeff(tab.get_zrow(q).coeff); - if (z_coeff == 1.) { - circ.add_op(OpType::Rz, node.theta(), {q}); - } else { - circ.add_op(OpType::Rz, -node.theta(), {q}); + case PauliNodeType::MidMeasure: { + if (node_ptr->tqe_cost() > 0) continue; + MidMeasure& node = dynamic_cast(*node_ptr); + auto [q_index, supp] = node.first_support(); + // Conjugate the Pauli to +Z + switch (supp) { + case Pauli::Z: { + if (!node.sign()) { + circ.add_op(OpType::X, {q_index}); + } + circ.add_measure(q_index, node.bit()); + if (!node.sign()) { + circ.add_op(OpType::X, {q_index}); + } + break; + } + case Pauli::X: { + circ.add_op(OpType::H, {q_index}); + if (!node.sign()) { + circ.add_op(OpType::X, {q_index}); + } + circ.add_measure(q_index, node.bit()); + if (!node.sign()) { + circ.add_op(OpType::X, {q_index}); + } + circ.add_op(OpType::H, {q_index}); + break; + } + case Pauli::Y: { + if (!node.sign()) { + circ.add_op(OpType::Vdg, {q_index}); + } else { + circ.add_op(OpType::V, {q_index}); + } + circ.add_measure(q_index, node.bit()); + if (!node.sign()) { + circ.add_op(OpType::V, {q_index}); + } else { + circ.add_op(OpType::Vdg, {q_index}); + } + break; + } + default: { + TKET_ASSERT(false); + } + } + first_set.erase(first_set.begin() + i); + break; } - break; - } - case 2: { - Complex x_coeff = - cast_coeff(tab.get_xrow(q).coeff); - if (x_coeff == 1.) { - circ.add_op(OpType::Rx, node.theta(), {q}); - } else { - circ.add_op(OpType::Rx, -node.theta(), {q}); + case PauliNodeType::ClassicalNode: { + // always implement Classical nodes + ClassicalNode& node = dynamic_cast(*node_ptr); + circ.add_op(node.op(), node.args()); + first_set.erase(first_set.begin() + i); + break; + } + case PauliNodeType::ConditionalBlock: { + // conditionals are implemented as a conditioned sequence of + // PauliExpBoxes and subsequently optimised by recursively calling + // greedy_pauli_optimisation + ConditionalBlock& node = dynamic_cast(*node_ptr); + const std::vector cond_bits = node.cond_bits(); + const unsigned cond_value = node.cond_value(); + std::vector qubits; + for (unsigned i = 0; i < circ.n_qubits(); i++) { + qubits.push_back(i); + } + Circuit cond_circ(circ.n_qubits()); + for (const auto& t : node.rotations()) { + const std::vector& string = std::get<0>(t); + bool sign = std::get<1>(t); + Expr angle = sign ? std::get<2>(t) : -std::get<2>(t); + Op_ptr peb_op = + std::make_shared(SymPauliTensor(string, angle)); + cond_circ.add_op(peb_op, qubits); + } + greedy_pauli_optimisation(discount_rate, depth_weight) + .apply(cond_circ); + // replace implicit wire swaps + cond_circ.replace_all_implicit_wire_swaps(); + Op_ptr cond = std::make_shared( + std::make_shared(cond_circ), (unsigned)cond_bits.size(), + cond_value); + std::vector args = cond_bits; + for (unsigned i = 0; i < cond_circ.n_qubits(); i++) { + args.push_back(i); + } + circ.add_op(cond, args); + first_set.erase(first_set.begin() + i); + break; + } + case PauliNodeType::PauliRotation: { + if (node_ptr->tqe_cost() > 0) continue; + PauliRotation& node = dynamic_cast(*node_ptr); + auto [q_index, supp] = node.first_support(); + depth_tracker.add_1q_gate(q_index); + OpType rot_type; + switch (supp) { + case Pauli::Y: { + rot_type = OpType::Ry; + break; + } + case Pauli::Z: { + rot_type = OpType::Rz; + break; + } + case Pauli::X: { + rot_type = OpType::Rx; + break; + } + default: + // support can't be Pauli::I + TKET_ASSERT(false); + } + circ.add_op(rot_type, node.angle(), {q_index}); + first_set.erase(first_set.begin() + i); + break; } - break; + default: + TKET_ASSERT(false); } - default: - // support can't be Pauli::I - TKET_ASSERT(false); } - bin.push_back(i); - } - if (bin.size() == 0) return false; - // sort the bin so we remove elements from back to front - std::sort(bin.begin(), bin.end(), std::greater()); - for (const unsigned& index : bin) { - first_set.erase(first_set.begin() + index); - } - if (first_set.size() == 0) { - rotation_sets.erase(rotation_sets.begin()); - return true; + if (first_set.empty()) { + rotation_sets.erase(rotation_sets.begin()); + if (rotation_sets.empty()) { + return; + } + } else { + return; + } } - return false; } /** * @brief Synthesise a vector of unordered rotation sets */ static void pauli_exps_synthesis( - std::vector>& rotation_sets, - std::vector& rows, UnitaryRevTableau& tab, Circuit& circ, - double discount_rate, double depth_weight, DepthTracker& depth_tracker) { + std::vector>& rotation_sets, + std::vector& rows, Circuit& circ, + DepthTracker& depth_tracker, double discount_rate, double depth_weight, + unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, + bool allow_zzphase) { while (true) { - while (consume_available_rotations( - rotation_sets, tab, circ, depth_tracker)); // do nothing - if (rotation_sets.size() == 0) break; - std::vector& first_set = rotation_sets[0]; + consume_nodes( + rotation_sets, circ, depth_tracker, discount_rate, depth_weight); + if (rotation_sets.empty()) break; + std::vector& first_set = rotation_sets[0]; // get nodes with min cost std::vector min_nodes_indices = {0}; - unsigned min_cost = first_set[0].tqe_cost(); + unsigned min_cost = first_set[0]->tqe_cost(); for (unsigned i = 1; i < first_set.size(); i++) { - unsigned node_cost = first_set[i].tqe_cost(); + unsigned node_cost = first_set[i]->tqe_cost(); if (node_cost == min_cost) { min_nodes_indices.push_back(i); } else if (node_cost < min_cost) { @@ -688,318 +625,167 @@ static void pauli_exps_synthesis( } std::set tqe_candidates; for (const unsigned& index : min_nodes_indices) { - std::vector node_reducing_tqes = first_set[index].reduction_tqes(); + std::vector node_reducing_tqes = first_set[index]->reduction_tqes(); tqe_candidates.insert( node_reducing_tqes.begin(), node_reducing_tqes.end()); } + // sample + std::vector sampled_tqes = + sample_tqes(tqe_candidates, max_tqe_candidates, seed); // for each tqe we compute costs which might subject to normalisation std::map> tqe_candidates_cost; - for (const TQE& tqe : tqe_candidates) { + for (const TQE& tqe : sampled_tqes) { tqe_candidates_cost.insert( {tqe, - {default_pauliexp_tqe_cost(discount_rate, rotation_sets, rows, tqe), - static_cast(depth_tracker.gate_depth( - std::get<1>(tqe), std::get<2>(tqe)))}}); + {default_pauliexp_tqe_cost( + discount_rate, rotation_sets, rows, tqe, max_lookahead), + static_cast(depth_tracker.gate_depth(tqe.a, tqe.b))}}); + } + std::map> rot2q_gates_cost; + if (allow_zzphase) { + // implementing a 2q rotation directly will result in a + // -1 tqe cost change in the first rotation set and 0 elsewhere. + // If multiple 2q rotations are worth implementing directly, we + // implement all of them to avoid doing the same cost calculation + // in the next rounds. + for (unsigned i = 0; i < first_set.size(); i++) { + if (first_set[i]->get_type() == PauliNodeType::PauliRotation && + first_set[i]->tqe_cost() == 1) { + const PauliRotation& node = + dynamic_cast(*first_set[i]); + std::vector supps; + std::vector paulis; + for (unsigned j = 0; j < node.string().size(); j++) { + if (node.string()[j] != Pauli::I) { + supps.push_back(j); + paulis.push_back(node.string()[j]); + } + } + rot2q_gates_cost.insert( + {{paulis[0], paulis[1], supps[0], supps[1], node.angle(), i}, + {-1, static_cast( + depth_tracker.gate_depth(supps[0], supps[1]))}}); + } + } } // select the best one - TQE selected_tqe = select_pauliexp_tqe(tqe_candidates_cost, depth_weight); - // apply TQE - apply_tqe_to_circ(selected_tqe, circ); - apply_tqe_to_tableau(selected_tqe, tab); - depth_tracker.add_2q_gate( - std::get<1>(selected_tqe), std::get<2>(selected_tqe)); - for (std::vector& rotation_set : rotation_sets) { - for (PauliExpNode& node : rotation_set) { - node.update(selected_tqe); + auto [min_tqes, min_rot2qs] = minmax_selection( + tqe_candidates_cost, rot2q_gates_cost, {1, depth_weight}); + if (min_rot2qs.empty()) { + TQE selected_tqe = sample_random_tqe(min_tqes, seed); + // apply TQE + apply_tqe_to_circ(selected_tqe, circ); + depth_tracker.add_2q_gate(selected_tqe.a, selected_tqe.b); + for (std::vector& rotation_set : rotation_sets) { + for (PauliNode_ptr& node : rotation_set) { + node->update(selected_tqe); + } + } + for (PauliNode_ptr& row : rows) { + row->update(selected_tqe); + } + } else { + // apply 2q rotations directly + std::sort( + min_rot2qs.begin(), min_rot2qs.end(), + [](const Rotation2Q& r1, const Rotation2Q& r2) { + return r1.index > r2.index; + }); + for (const Rotation2Q& rot : min_rot2qs) { + apply_rot2q_to_circ(rot, circ); + first_set.erase(first_set.begin() + rot.index); } - } - for (TableauRowNode& row : rows) { - row.update(selected_tqe); } } } -// convert a Pauli exponential to a PauliExpNode -static PauliExpNode get_node_from_exp( - const std::vector& paulis, const Expr& theta, - const qubit_vector_t& args, unsigned n, const UnitaryTableau& forward_tab, - const UnitaryRevTableau& tab) { - std::map pauli_map; - for (unsigned i = 0; i < args.size(); i++) { - pauli_map.insert({args[i], paulis[i]}); +Circuit greedy_pauli_set_synthesis( + const std::vector& unordered_set, double depth_weight, + unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, + bool allow_zzphase) { + if (max_lookahead == 0) { + throw GreedyPauliSimpError("max_lookahead must be greater than 0."); } - // this has the effect of bringing the final clifford - // forward past the Pauli exponential - SpPauliStabiliser pstab = - forward_tab.get_row_product(SpPauliStabiliser(pauli_map)); - Complex sign = cast_coeff(pstab.coeff); - - std::vector support_vec; - for (unsigned i = 0; i < n; i++) { - SpPauliStabiliser zrow = tab.get_zrow(Qubit(i)); - SpPauliStabiliser xrow = tab.get_xrow(Qubit(i)); - bool z_supp = !zrow.commutes_with(pstab); - bool x_supp = !xrow.commutes_with(pstab); - if (!z_supp && !x_supp) { - support_vec.push_back(0); - } else if (!z_supp && x_supp) { - support_vec.push_back(1); - } else if (z_supp && !x_supp) { - support_vec.push_back(2); - } else if (z_supp && x_supp) { - support_vec.push_back(3); - } + if (max_tqe_candidates == 0) { + throw GreedyPauliSimpError("max_tqe_candidates must be greater than 0."); } - return PauliExpNode(support_vec, sign.real() * theta); -} -// detect trivial pauli exps, if true then return the global phase -static std::pair is_trivial_pauliexp( - const std::vector& paulis, const Expr& theta) { - if (static_cast(std::count( - paulis.begin(), paulis.end(), Pauli::I)) == paulis.size()) { - // If all identity term - return {true, -theta / 2}; - } - if (equiv_0(theta, 2)) { - if (equiv_0(theta, 4)) { - return {true, 0}; - } else { - return {true, -1}; - } - } - return {false, 0}; -} -Circuit greedy_pauli_set_synthesis( - const std::vector& unordered_set, double depth_weight) { if (unordered_set.size() == 0) { return Circuit(); } unsigned n_qubits = unordered_set[0].string.size(); - Circuit c(n_qubits); - std::vector> rotation_sets{{}}; - std::vector rows; - for (auto& pauli : unordered_set) { - std::vector support_vec; - TKET_ASSERT(pauli.string.size() == n_qubits); - for (unsigned i = 0; i < n_qubits; i++) { - if (pauli.string[i] == Pauli::I) { - support_vec.push_back(0); - } else if (pauli.string[i] == Pauli::Z) { - support_vec.push_back(1); - } else if (pauli.string[i] == Pauli::X) { - support_vec.push_back(2); - } else { - support_vec.push_back(3); - } - } - rotation_sets[0].push_back(PauliExpNode(support_vec, pauli.coeff)); - } - UnitaryRevTableau tab(n_qubits); - // add identity TableauRowNodes - for (unsigned i = 0; i < n_qubits; i++) { - std::vector support_vec; - // identity rows - std::map p; - std::map q; - for (unsigned j = 0; j < n_qubits; j++) { - if (j == i) { - p.insert({Qubit(j), Pauli::Z}); - q.insert({Qubit(j), Pauli::X}); - } else { - p.insert({Qubit(j), Pauli::I}); - q.insert({Qubit(j), Pauli::I}); - } - } - SpPauliStabiliser stab_p(p); - SpPauliStabiliser stab_q(q); - for (unsigned row_index = 0; row_index < n_qubits; row_index++) { - SpPauliStabiliser zrow = tab.get_zrow(Qubit(row_index)); - SpPauliStabiliser xrow = tab.get_xrow(Qubit(row_index)); - bool lpx = !xrow.commutes_with(stab_p); - bool lpz = !zrow.commutes_with(stab_p); - bool lqx = !xrow.commutes_with(stab_q); - bool lqz = !zrow.commutes_with(stab_q); - support_vec.push_back(8 * lpx + 4 * lpz + 2 * lqx + lqz); - } - rows.push_back(TableauRowNode(support_vec)); - } + auto [rotation_set, rows] = gpg_from_unordered_set(unordered_set); + std::vector> rotation_sets{rotation_set}; DepthTracker depth_tracker(n_qubits); // synthesise Pauli exps pauli_exps_synthesis( - rotation_sets, rows, tab, c, 0, depth_weight, depth_tracker); + rotation_sets, rows, c, depth_tracker, 0, depth_weight, max_lookahead, + max_tqe_candidates, seed, allow_zzphase); // synthesise the tableau - tableau_row_nodes_synthesis(rows, tab, c, depth_weight, depth_tracker); + tableau_row_nodes_synthesis( + rows, c, depth_tracker, depth_weight, max_lookahead, max_tqe_candidates, + seed); c.replace_SWAPs(); return c; } Circuit greedy_pauli_graph_synthesis( - const Circuit& circ, double discount_rate, double depth_weight) { - // c is the circuit we are trying to build - Circuit c(circ.all_qubits(), circ.all_bits()); - std::optional name = circ.get_name(); - if (name != std::nullopt) { - c.set_name(name.value()); + const Circuit& circ, double discount_rate, double depth_weight, + unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, + bool allow_zzphase) { + if (max_lookahead == 0) { + throw GreedyPauliSimpError("max_lookahead must be greater than 0."); + } + if (max_tqe_candidates == 0) { + throw GreedyPauliSimpError("max_tqe_candidates must be greater than 0."); } - c.add_phase(circ.get_phase()); - unit_map_t unit_map = c.flatten_registers(); - Circuit measure_circ(c.n_qubits(), c.n_bits()); - Circuit cliff(c.n_qubits()); - // circuit used to iterate the original commands with flattened registers Circuit circ_flat(circ); - circ_flat.flatten_registers(); - std::vector commands = circ_flat.get_commands(); - // extract the final clifford and the measurement circuits - for (const Command& cmd : commands) { - OpType optype = cmd.get_op_ptr()->get_type(); - switch (optype) { - case OpType::Measure: { - measure_circ.add_op(OpType::Measure, cmd.get_args()); - break; - } - default: { - if (optype == OpType::PauliExpBox || - optype == OpType::PauliExpPairBox || - optype == OpType::PauliExpCommutingSetBox) - break; - TKET_ASSERT(is_clifford_type(optype) && is_gate_type(optype)); - cliff.add_op(optype, cmd.get_args()); - } - } - } - std::vector> rotation_sets; - std::vector rows; - // use forward Tableau to update the paulis by commuting the tableau to the - // front - UnitaryTableau forward_tab = circuit_to_unitary_tableau(cliff); - // Tableau used for tracking Cliffords throughout the synthesis - // TODO: this can be potentially made redundant - UnitaryRevTableau tab = circuit_to_unitary_rev_tableau(cliff).dagger(); - unsigned n_qubits = c.n_qubits(); - // extract the Pauli exps - for (const Command& cmd : commands) { - OpType optype = cmd.get_op_ptr()->get_type(); - switch (optype) { - case OpType::PauliExpBox: { - const PauliExpBox& pbox = - static_cast(*cmd.get_op_ptr()); - const Expr phase = pbox.get_phase(); - const std::vector paulis = pbox.get_paulis(); - auto [trivial, global_phase] = is_trivial_pauliexp(paulis, phase); - if (trivial) { - c.add_phase(global_phase); - } else { - rotation_sets.push_back({get_node_from_exp( - paulis, phase, cmd.get_qubits(), n_qubits, forward_tab, tab)}); - } - break; - } - case OpType::PauliExpPairBox: { - const PauliExpPairBox& pbox = - static_cast(*cmd.get_op_ptr()); - const auto [paulis1, paulis2] = pbox.get_paulis_pair(); - const auto [phase1, phase2] = pbox.get_phase_pair(); - auto [trivial1, global_phase1] = is_trivial_pauliexp(paulis1, phase1); - auto [trivial2, global_phase2] = is_trivial_pauliexp(paulis2, phase2); - std::vector rotation_set; - if (trivial1) { - c.add_phase(global_phase1); - } else { - rotation_set.push_back(get_node_from_exp( - paulis1, phase1, cmd.get_qubits(), n_qubits, forward_tab, tab)); - } - if (trivial2) { - c.add_phase(global_phase2); - } else { - rotation_set.push_back(get_node_from_exp( - paulis2, phase2, cmd.get_qubits(), n_qubits, forward_tab, tab)); - } - if (!rotation_set.empty()) { - rotation_sets.push_back(rotation_set); - } - break; - } - case OpType::PauliExpCommutingSetBox: { - const PauliExpCommutingSetBox& pbox = - static_cast(*cmd.get_op_ptr()); - const std::vector gadgets = pbox.get_pauli_gadgets(); - std::vector rotation_set; - for (const SymPauliTensor& pt : gadgets) { - const std::vector paulis = pt.string; - const Expr phase = pt.coeff; - auto [trivial, global_phase] = is_trivial_pauliexp(paulis, phase); - if (trivial) { - c.add_phase(global_phase); - } else { - rotation_set.push_back(get_node_from_exp( - paulis, phase, cmd.get_qubits(), n_qubits, forward_tab, tab)); - } - } - if (rotation_set.size() > 0) { - rotation_sets.push_back(rotation_set); - } - break; - } - default: - break; - } + unsigned n_qubits = circ_flat.n_qubits(); + unsigned n_bits = circ_flat.n_bits(); + // empty circuit + Circuit new_circ(n_qubits, n_bits); + std::optional name = circ_flat.get_name(); + if (name != std::nullopt) { + new_circ.set_name(name.value()); } - // add identity TableauRowNodes - for (unsigned i = 0; i < n_qubits; i++) { - std::vector support_vec; - // identity rows - std::map p; - std::map q; - for (unsigned j = 0; j < n_qubits; j++) { - if (j == i) { - p.insert({Qubit(j), Pauli::Z}); - q.insert({Qubit(j), Pauli::X}); - } else { - p.insert({Qubit(j), Pauli::I}); - q.insert({Qubit(j), Pauli::I}); - } - } - SpPauliStabiliser stab_p(p); - SpPauliStabiliser stab_q(q); - for (unsigned row_index = 0; row_index < n_qubits; row_index++) { - SpPauliStabiliser zrow = tab.get_zrow(Qubit(row_index)); - SpPauliStabiliser xrow = tab.get_xrow(Qubit(row_index)); - bool lpx = !xrow.commutes_with(stab_p); - bool lpz = !zrow.commutes_with(stab_p); - bool lqx = !xrow.commutes_with(stab_q); - bool lqz = !zrow.commutes_with(stab_q); - support_vec.push_back(8 * lpx + 4 * lpz + 2 * lqx + lqz); - } - rows.push_back(TableauRowNode(support_vec)); + unit_map_t unit_map = circ_flat.flatten_registers(); + unit_map_t rev_unit_map; + for (const auto& pair : unit_map) { + rev_unit_map.insert({pair.second, pair.first}); } + GPGraph gpg(circ_flat); + auto [rotation_sets, rows, measures] = gpg.get_sequence(); DepthTracker depth_tracker(n_qubits); // synthesise Pauli exps pauli_exps_synthesis( - rotation_sets, rows, tab, c, discount_rate, depth_weight, depth_tracker); + rotation_sets, rows, new_circ, depth_tracker, discount_rate, depth_weight, + max_lookahead, max_tqe_candidates, seed, allow_zzphase); // synthesise the tableau - tableau_row_nodes_synthesis(rows, tab, c, depth_weight, depth_tracker); - unit_map_t rev_unit_map; - for (const auto& pair : unit_map) { - rev_unit_map.insert({pair.second, pair.first}); - } - c.append(measure_circ); - c.rename_units(rev_unit_map); - c.replace_SWAPs(); - return c; + tableau_row_nodes_synthesis( + rows, new_circ, depth_tracker, depth_weight, max_lookahead, + max_tqe_candidates, seed); + for (auto it = measures.begin(); it != measures.end(); ++it) { + new_circ.add_measure(it->left, it->right); + } + new_circ.rename_units(rev_unit_map); + new_circ.replace_SWAPs(); + return new_circ; } } // namespace GreedyPauliSimp -Transform greedy_pauli_optimisation(double discount_rate, double depth_weight) { - return Transform([discount_rate, depth_weight](Circuit& circ) { - synthesise_pauli_graph(PauliSynthStrat::Sets, CXConfigType::Snake) - .apply(circ); +Transform greedy_pauli_optimisation( + double discount_rate, double depth_weight, unsigned max_lookahead, + unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase) { + return Transform([discount_rate, depth_weight, max_lookahead, + max_tqe_candidates, seed, allow_zzphase](Circuit& circ) { circ = GreedyPauliSimp::greedy_pauli_graph_synthesis( - circ, discount_rate, depth_weight); - singleq_clifford_sweep().apply(circ); + circ, discount_rate, depth_weight, max_lookahead, max_tqe_candidates, + seed, allow_zzphase); + // decompose the conditional CircBoxes + circ.decompose_boxes_recursively(); return true; }); } diff --git a/tket/src/Transformations/PauliOptimisation.cpp b/tket/src/Transformations/PauliOptimisation.cpp index 34f3e2d749..8022ba7c39 100644 --- a/tket/src/Transformations/PauliOptimisation.cpp +++ b/tket/src/Transformations/PauliOptimisation.cpp @@ -16,6 +16,7 @@ #include "tket/Circuit/CircUtils.hpp" #include "tket/Converters/Converters.hpp" +#include "tket/OpType/EdgeType.hpp" #include "tket/OpType/OpType.hpp" #include "tket/OpType/OpTypeInfo.hpp" #include "tket/PauliGraph/PauliGraph.hpp" @@ -238,7 +239,13 @@ Transform special_UCC_synthesis(PauliSynthStrat strat, CXConfigType cx_config) { Circuit inner_circ = *(box_ptr->to_circuit()); synther.apply(inner_circ); decomp_boxes().apply(inner_circ); - Subcircuit sub = {circ.get_in_edges(v), circ.get_all_out_edges(v), {v}}; + Subcircuit sub = { + circ.get_in_edges_of_type(v, EdgeType::Quantum), + circ.get_out_edges_of_type(v, EdgeType::Quantum), + circ.get_in_edges_of_type(v, EdgeType::Classical), + circ.get_out_edges_of_type(v, EdgeType::Classical), + circ.get_in_edges_of_type(v, EdgeType::Boolean), + {v}}; circ.substitute(inner_circ, sub); } return !circbox_verts diff --git a/tket/test/CMakeLists.txt b/tket/test/CMakeLists.txt index f7d895fb00..4e020c4d16 100644 --- a/tket/test/CMakeLists.txt +++ b/tket/test/CMakeLists.txt @@ -107,6 +107,7 @@ add_executable(test-tket src/test_Assertion.cpp src/test_BoxDecompRoutingMethod.cpp src/test_ChoiMixTableau.cpp + src/test_ClExpr.cpp src/test_Clifford.cpp src/test_Combinators.cpp src/test_CompilerPass.cpp diff --git a/tket/test/src/test_ClExpr.cpp b/tket/test/src/test_ClExpr.cpp new file mode 100644 index 0000000000..a49888ce5e --- /dev/null +++ b/tket/test/src/test_ClExpr.cpp @@ -0,0 +1,217 @@ +// Copyright 2019-2024 Cambridge Quantum Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "tket/Circuit/Circuit.hpp" +#include "tket/Circuit/Command.hpp" +#include "tket/OpType/EdgeType.hpp" +#include "tket/Ops/ClExpr.hpp" +#include "tket/Ops/OpPtr.hpp" +#include "tket/Utils/UnitID.hpp" + +namespace tket { + +SCENARIO("Circuit containing a ClExprOp") { + GIVEN("A simple classical expression") { + // AND of two bits: + ClExpr expr(ClOp::BitAnd, {ClBitVar{0}, ClBitVar{1}}); + // First two bits are inputs; last bit is output: + WiredClExpr wexpr(expr, {{0, 0}, {1, 1}}, {}, {2}); + Op_ptr op = std::make_shared(wexpr); + REQUIRE(op->get_signature() == op_signature_t(3, EdgeType::Classical)); + Circuit circ(0, 3); + circ.add_op(op, {0, 1, 2}); + std::vector cmds = circ.get_commands(); + REQUIRE(cmds.size() == 1); + } + GIVEN("A complicated classical expression") { + // d[0,1,2] <-- (a[2,1,0] + b[2,3,4]) / (c[1,0,3] * d[0,1,2]) + ClExpr numer(ClOp::RegAdd, {ClRegVar{0}, ClRegVar{1}}); + ClExpr denom(ClOp::RegMul, {ClRegVar{2}, ClRegVar{3}}); + ClExpr expr(ClOp::RegDiv, {numer, denom}); + std::vector a_pos{0, 3, 4}; + std::vector b_pos{1, 11, 5}; + std::vector c_pos{10, 2, 7}; + std::vector d_pos{8, 9, 6}; + WiredClExpr wexpr( + expr, {}, {{0, a_pos}, {1, b_pos}, {2, c_pos}, {3, d_pos}}, d_pos); + std::vector e_pos{0, 1, 2}; + REQUIRE_THROWS_AS( + WiredClExpr( + expr, {}, {{0, a_pos}, {1, b_pos}, {2, e_pos}, {3, d_pos}}, d_pos), + ClExprWiringError); + Op_ptr op = std::make_shared(wexpr); + Circuit circ; + register_t preg = circ.add_c_register("p", 6); + register_t qreg = circ.add_c_register("q", 6); + circ.add_op( + op, {Bit{"p", 2}, Bit{"q", 2}, Bit{"p", 1}, Bit{"q", 3}, Bit{"p", 0}, + Bit{"q", 4}, Bit{"p", 5}, Bit{"q", 5}, Bit{"p", 4}, Bit{"q", 0}, + Bit{"p", 3}, Bit{"q", 1}}); + std::vector cmds = circ.get_commands(); + REQUIRE(cmds.size() == 1); + } +} + +SCENARIO("Serialization and stringification") { + GIVEN("ClOp") { + ClOp op = ClOp::RegEq; + std::stringstream ss; + ss << op; + REQUIRE(ss.str() == "eq"); + nlohmann::json j = op; + ClOp op1 = j.get(); + REQUIRE(op1 == op); + } + GIVEN("All ClOps") { + std::stringstream ss; + ss << ClOp::INVALID << " " << ClOp::BitAnd << " " << ClOp::BitOr << " " + << ClOp::BitXor << " " << ClOp::BitEq << " " << ClOp::BitNeq << " " + << ClOp::BitNot << " " << ClOp::BitZero << " " << ClOp::BitOne << " " + << ClOp::RegAnd << " " << ClOp::RegOr << " " << ClOp::RegXor << " " + << ClOp::RegEq << " " << ClOp::RegNeq << " " << ClOp::RegNot << " " + << ClOp::RegZero << " " << ClOp::RegOne << " " << ClOp::RegLt << " " + << ClOp::RegGt << " " << ClOp::RegLeq << " " << ClOp::RegGeq << " " + << ClOp::RegAdd << " " << ClOp::RegSub << " " << ClOp::RegMul << " " + << ClOp::RegDiv << " " << ClOp::RegPow << " " << ClOp::RegLsh << " " + << ClOp::RegRsh << " " << ClOp::RegNeg; + REQUIRE( + ss.str() == + "INVALID and or xor eq neq not zero one and or xor eq neq not zero one " + "lt gt leq geq add sub mul div pow lsh rsh neg"); + } + GIVEN("ClBitVar") { + ClBitVar var{3}; + std::stringstream ss; + ss << var; + REQUIRE(ss.str() == "b3"); + nlohmann::json j = var; + ClBitVar var1 = j.get(); + REQUIRE(var1 == var); + } + GIVEN("ClRegVar") { + ClRegVar var{4}; + std::stringstream ss; + ss << var; + REQUIRE(ss.str() == "r4"); + nlohmann::json j = var; + ClRegVar var1 = j.get(); + REQUIRE(var1 == var); + } + GIVEN("ClExprVar") { + ClExprVar var_bit = ClBitVar{3}; + ClExprVar var_reg = ClRegVar{4}; + std::stringstream ss; + ss << var_bit << ", " << var_reg; + REQUIRE(ss.str() == "b3, r4"); + nlohmann::json j_bit = var_bit; + nlohmann::json j_reg = var_reg; + ClExprVar var_bit1 = j_bit.get(); + ClExprVar var_reg1 = j_reg.get(); + REQUIRE(var_bit1 == var_bit); + REQUIRE(var_reg1 == var_reg); + } + GIVEN("ClExprTerm") { + ClExprTerm term_int = 7; + ClExprTerm term_var = ClRegVar{5}; + std::stringstream ss; + ss << term_int << ", " << term_var; + REQUIRE(ss.str() == "7, r5"); + nlohmann::json j_int = term_int; + nlohmann::json j_var = term_var; + ClExprTerm term_int1 = j_int.get(); + ClExprTerm term_var1 = j_var.get(); + REQUIRE(term_int1 == term_int); + REQUIRE(term_var1 == term_var); + } + GIVEN("Vector of ClExprArg (1)") { + std::vector args{ClRegVar{2}, int{3}}; + nlohmann::json j = args; + std::vector args1 = j.get>(); + REQUIRE(args == args1); + } + GIVEN("ClExpr (1)") { + // r0 + 7 + ClExpr expr(ClOp::RegAdd, {ClRegVar{0}, int{7}}); + std::stringstream ss; + ss << expr; + REQUIRE(ss.str() == "add(r0, 7)"); + nlohmann::json j = expr; + ClExpr expr1 = j.get(); + REQUIRE(expr1 == expr); + } + GIVEN("Vector of ClExprArg (2)") { + ClExpr expr(ClOp::RegAdd, {ClRegVar{0}, int{8}}); + std::vector args{expr}; + nlohmann::json j = args; + std::vector args1 = j.get>(); + REQUIRE(args == args1); + } + GIVEN("ClExpr (2)") { + // (r0 + r1) / (r2 * 3) + ClExpr numer(ClOp::RegAdd, {ClRegVar{0}, ClRegVar{1}}); + ClExpr denom(ClOp::RegMul, {ClRegVar{2}, int{3}}); + ClExpr expr(ClOp::RegDiv, {numer, denom}); + std::stringstream ss; + ss << expr; + REQUIRE(ss.str() == "div(add(r0, r1), mul(r2, 3))"); + nlohmann::json j = expr; + ClExpr expr1 = j.get(); + REQUIRE(expr1 == expr); + } + GIVEN("WiredClExpr") { + ClExpr numer(ClOp::RegAdd, {ClRegVar{0}, ClRegVar{1}}); + ClExpr denom(ClOp::RegMul, {ClRegVar{2}, ClRegVar{3}}); + ClExpr expr(ClOp::RegDiv, {numer, denom}); + std::vector a_pos{0, 3, 4}; + std::vector b_pos{1, 11, 5}; + std::vector c_pos{10, 2, 7}; + std::vector d_pos{8, 9, 6}; + WiredClExpr wexpr( + expr, {}, {{0, a_pos}, {1, b_pos}, {2, c_pos}, {3, d_pos}}, d_pos); + std::stringstream ss; + ss << wexpr; + REQUIRE( + ss.str() == + "div(add(r0, r1), mul(r2, r3)) [r0:(0,3,4), r1:(1,11,5), r2:(10,2,7), " + "r3:(8,9,6) --> (8,9,6)]"); + nlohmann::json j = wexpr; + WiredClExpr wexpr1 = j.get(); + REQUIRE(wexpr1 == wexpr); + } + GIVEN("ClExprOp") { + ClExpr numer(ClOp::RegAdd, {ClRegVar{0}, ClRegVar{1}}); + ClExpr denom(ClOp::RegMul, {ClRegVar{2}, ClRegVar{3}}); + ClExpr expr(ClOp::RegDiv, {numer, denom}); + std::vector a_pos{0, 3, 4}; + std::vector b_pos{1, 11, 5}; + std::vector c_pos{10, 2, 7}; + std::vector d_pos{8, 9, 6}; + WiredClExpr wexpr( + expr, {}, {{0, a_pos}, {1, b_pos}, {2, c_pos}, {3, d_pos}}, d_pos); + Op_ptr op = std::make_shared(wexpr); + nlohmann::json j = op; + Op_ptr op1 = j.get(); + const ClExprOp& exprop = static_cast(*op1); + REQUIRE(exprop.get_wired_expr() == wexpr); + Op_ptr op2 = op->symbol_substitution({}); + REQUIRE(op2->free_symbols().empty()); + } +} + +} // namespace tket diff --git a/tket/test/src/test_GreedyPauli.cpp b/tket/test/src/test_GreedyPauli.cpp index 16250ca8c8..366143ece4 100644 --- a/tket/test/src/test_GreedyPauli.cpp +++ b/tket/test/src/test_GreedyPauli.cpp @@ -20,7 +20,7 @@ #include "tket/Circuit/PauliExpBoxes.hpp" #include "tket/Circuit/Simulation/CircuitSimulator.hpp" #include "tket/Gate/SymTable.hpp" -#include "tket/PauliGraph/PauliGraph.hpp" +#include "tket/Ops/ClassicalOps.hpp" #include "tket/Predicates/PassGenerators.hpp" #include "tket/Transformations/GreedyPauliOptimisation.hpp" #include "tket/Utils/Expression.hpp" @@ -28,36 +28,20 @@ namespace tket { namespace test_GreedyPauliSimp { -SCENARIO("Unsupported circuits") { - GIVEN("Circuit with mid-circ measurements") { - Circuit circ(2, 2); - circ.add_op(OpType::H, {0}); - circ.add_op(OpType::Rx, 0.5, {1}); - circ.add_op(OpType::Measure, {0, 0}); - circ.add_op(OpType::CX, {0, 1}); - REQUIRE_THROWS_MATCHES( - Transforms::greedy_pauli_optimisation().apply(circ), - MidCircuitMeasurementNotAllowed, - MessageContains( - "PauliGraph does not support mid-circuit measurements")); - } - GIVEN("Circuit with resets") { +SCENARIO("Exception handling") { + GIVEN("Invalid arguments") { Circuit circ(1); - circ.add_op(OpType::H, {0}); - circ.add_op(OpType::Reset, {0}); REQUIRE_THROWS_MATCHES( - Transforms::greedy_pauli_optimisation().apply(circ), BadOpType, - MessageContains("Cannot add gate to PauliGraph")); - } - GIVEN("Circuit with conditional gates") { - Circuit circ(2, 2); - circ.add_conditional_gate(OpType::Rz, {0.5}, {0}, {0}, 0); + Transforms::greedy_pauli_optimisation(0.3, 0.3, 0, 10).apply(circ), + Transforms::GreedyPauliSimp::GreedyPauliSimpError, + MessageContains("max_lookahead must be greater than 0.")); REQUIRE_THROWS_MATCHES( - Transforms::greedy_pauli_optimisation().apply(circ), BadOpType, - MessageContains( - "Can only make a PauliGraph from a circuit of basic gates")); + Transforms::greedy_pauli_optimisation(0.3, 0.3, 10, 0).apply(circ), + Transforms::GreedyPauliSimp::GreedyPauliSimpError, + MessageContains("max_tqe_candidates must be greater than 0.")); } } + SCENARIO("Clifford synthesis") { GIVEN("Empty circuit") { Circuit circ(3); @@ -130,6 +114,26 @@ SCENARIO("Clifford synthesis") { REQUIRE(Transforms::greedy_pauli_optimisation().apply(d)); REQUIRE(test_unitary_comparison(circ, d, true)); } + GIVEN("Test search limits") { + Circuit circ(4); + circ.add_op(OpType::X, {0}); + circ.add_op(OpType::SWAP, {1, 2}); + circ.add_op(OpType::CX, {0, 2}); + circ.add_op(OpType::H, {3}); + circ.add_op(OpType::CZ, {1, 3}); + circ.add_op(OpType::H, {2}); + circ.add_op(OpType::CX, {3, 2}); + circ.add_op(OpType::Z, {2}); + circ.add_op(OpType::SWAP, {3, 1}); + circ.add_op(OpType::CY, {0, 2}); + Circuit d1(circ); + Circuit d2(circ); + REQUIRE(Transforms::greedy_pauli_optimisation(0.7, 0.3, 2, 1).apply(d1)); + REQUIRE(Transforms::greedy_pauli_optimisation(0.7, 0.3, 20, 20).apply(d2)); + REQUIRE(test_unitary_comparison(circ, d1, true)); + REQUIRE(test_unitary_comparison(circ, d2, true)); + REQUIRE(d1 != d2); + } } SCENARIO("Complete synthesis") { GIVEN("1Q Simple Circuit") { @@ -213,6 +217,55 @@ SCENARIO("Complete synthesis") { REQUIRE(Transforms::greedy_pauli_optimisation().apply(d)); REQUIRE(test_unitary_comparison(circ, d, true)); } + GIVEN("5Q PauliExp Circuit with search limits") { + Circuit circ(5); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::X, Pauli::X}, 0.3)), + {0, 1, 4}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::Z, Pauli::Y}, -0.1)), + {2, 3, 0}); + circ.add_box( + PauliExpPairBox( + SymPauliTensor({Pauli::X, Pauli::Z, Pauli::Y}, 1.0), + SymPauliTensor({Pauli::Z, Pauli::X, Pauli::Y}, 0.4)), + {0, 2, 4}); + circ.add_box( + PauliExpCommutingSetBox({ + {{ + Pauli::I, + Pauli::Y, + Pauli::I, + }, + -0.1}, + {{Pauli::X, Pauli::Y, Pauli::Z}, -1.2}, + {{Pauli::X, Pauli::Y, Pauli::Z}, 0.5}, + }), + {1, 2, 3}); + circ.add_box( + PauliExpCommutingSetBox({ + {{ + Pauli::I, + Pauli::X, + Pauli::I, + }, + -0.15}, + {{Pauli::X, Pauli::X, Pauli::Z}, -1.25}, + {{Pauli::X, Pauli::X, Pauli::Z}, 0.2}, + }), + {0, 3, 4}); + circ.add_op(OpType::CX, {0, 2}); + circ.add_op(OpType::SWAP, {2, 3}); + circ.add_op(OpType::H, {3}); + circ.add_op(OpType::CZ, {1, 3}); + Circuit d1(circ); + Circuit d2(circ); + REQUIRE(Transforms::greedy_pauli_optimisation(0.7, 0.3, 3, 3).apply(d1)); + REQUIRE(Transforms::greedy_pauli_optimisation(0.7, 0.3, 30, 30).apply(d2)); + REQUIRE(test_unitary_comparison(circ, d1, true)); + REQUIRE(test_unitary_comparison(circ, d2, true)); + REQUIRE(d1 != d2); + } GIVEN("Circuit with trivial Pauli exps") { Circuit circ(4); circ.add_box(PauliExpBox(SymPauliTensor({Pauli::X, Pauli::X}, 2)), {0, 1}); @@ -276,6 +329,385 @@ SCENARIO("Complete synthesis") { REQUIRE(Transforms::greedy_pauli_optimisation().apply(g)); REQUIRE(d == g); } + GIVEN("Circuit with conditional gates") { + Circuit circ(2, 2); + circ.add_op(OpType::CX, {0, 1}); + circ.add_conditional_gate(OpType::Rz, {0.5}, {0}, {0}, 0); + circ.add_op(OpType::CX, {0, 1}); + Circuit d(2, 2); + Op_ptr cond = std::make_shared( + std::make_shared( + SymPauliTensor({Pauli::Z, Pauli::I}, 0.5)), + 1, 0); + d.add_conditional_gate(OpType::Sdg, {}, {0}, {0}, 0); + d.add_conditional_gate(OpType::Z, {}, {0}, {0}, 0); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(circ)); + REQUIRE(circ == d); + } + GIVEN("Circuit with conditional gates 2") { + Circuit circ(2, 1); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::Z}, 0.12)), {0, 1}); + Op_ptr cond = std::make_shared( + std::make_shared( + SymPauliTensor({Pauli::Z, Pauli::Z}, 0.5)), + 1, 0); + circ.add_op(cond, {0, 0, 1}); + // two boxes anti-commute hence simultaneous diagonalisation + Circuit d(2, 1); + d.add_op(OpType::CY, {1, 0}); + d.add_op(OpType::Rx, 0.12, {0}); + d.add_conditional_gate(OpType::Sdg, {}, {0}, {0}, 0); + d.add_conditional_gate(OpType::Z, {}, {0}, {0}, 0); + d.add_op(OpType::CY, {1, 0}); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(circ)); + REQUIRE(circ == d); + } + GIVEN("Circuit with conditional gates and measures") { + Circuit circ(2, 2); + Op_ptr cond1 = std::make_shared( + std::make_shared( + SymPauliTensor({Pauli::Z, Pauli::X}, 0.5)), + 1, 0); + Op_ptr cond2 = std::make_shared( + std::make_shared( + SymPauliTensor({Pauli::Z, Pauli::Y}, 0.12)), + 1, 0); + circ.add_op(cond1, {0, 0, 1}); + circ.add_op(OpType::Measure, {0, 0}); + // can commute to the front + circ.add_op(cond2, {1, 0, 1}); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(circ)); + REQUIRE(circ.count_n_qubit_gates(2) == 3); + } + + GIVEN("Conditionals merging") { + Circuit circ(2, 2); + Op_ptr cond1 = std::make_shared( + std::make_shared( + SymPauliTensor({Pauli::Z, Pauli::X}, 0.25)), + 1, 0); + Op_ptr cond2 = std::make_shared( + std::make_shared( + SymPauliTensor({Pauli::Z, Pauli::X}, -0.25)), + 1, 0); + circ.add_op(cond1, {0, 0, 1}); + circ.add_op(OpType::Rz, 0.3, {0}); + circ.add_op(cond2, {0, 0, 1}); + circ.add_conditional_gate(OpType::CX, {}, {0, 1}, {0}, 0); + circ.add_conditional_gate(OpType::CX, {}, {0, 1}, {0}, 0); + circ.add_op(OpType::Rz, -0.3, {0}); + // should all be canceled + Circuit d(2, 2); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(circ)); + REQUIRE(circ == d); + } + GIVEN("Circuit with classical gates") { + Circuit circ(1, 4); + circ.add_op(OpType::H, {0}); + circ.add_op(ClassicalX(), {1}); + circ.add_op(ClassicalCX(), {0, 1}); + circ.add_op(AndWithOp(), {2, 3}); + Circuit d(circ); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(circ)); + REQUIRE(circ == d); + } + GIVEN("Circuit with WASMs") { + std::string wasm_file = "string/with/path/to/wasm/file"; + std::string wasm_func = "stringNameOfWASMFunc"; + std::vector uv = {2, 1}; + const std::shared_ptr wop_ptr = + std::make_shared(6, 1, uv, uv, wasm_func, wasm_file); + Circuit circ(1, 7); + circ.add_op(OpType::X, {0}); + circ.add_op( + wop_ptr, + {Bit(0), Bit(1), Bit(2), Bit(3), Bit(4), Bit(5), WasmState(0)}); + Circuit d(circ); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(circ)); + REQUIRE(circ == d); + } + GIVEN("Circuit with mid-circuit measurements") { + Circuit circ(2, 2); + circ.add_op(OpType::T, {0}); + circ.add_op(OpType::Measure, {0, 0}); + circ.add_op(OpType::Tdg, {0}); + circ.add_op(OpType::Measure, {1, 1}); + Circuit d(2, 2); + d.add_op(OpType::Measure, {0, 0}); + d.add_op(OpType::Measure, {1, 1}); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(circ)); + REQUIRE(circ == d); + } + GIVEN("Circuit with mid-circuit measurements 2") { + // -X + Circuit c1(1, 1); + c1.add_op(OpType::Z, {0}); + c1.add_op(OpType::H, {0}); + c1.add_op(OpType::Measure, {0, 0}); + c1.add_op(OpType::T, {0}); + Circuit d1(1, 1); + d1.add_op(OpType::H, {0}); + d1.add_op(OpType::X, {0}); + d1.add_op(OpType::Measure, {0, 0}); + d1.add_op(OpType::X, {0}); + d1.add_op(OpType::H, {0}); + d1.add_op(OpType::Rx, 3.75, {0}); + d1.add_op(OpType::H, {0}); + d1.add_op(OpType::X, {0}); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(c1)); + REQUIRE(c1 == d1); + // Y + Circuit c2(1, 1); + c2.add_op(OpType::V, {0}); + c2.add_op(OpType::Measure, {0, 0}); + c2.add_op(OpType::T, {0}); + Circuit d2(1, 1); + d2.add_op(OpType::V, {0}); + d2.add_op(OpType::Measure, {0, 0}); + d2.add_op(OpType::Vdg, {0}); + d2.add_op(OpType::Ry, 0.25, {0}); + d2.add_op(OpType::V, {0}); + // Vdg;Ry(0.25);V = T + REQUIRE(Transforms::greedy_pauli_optimisation().apply(c2)); + REQUIRE(c2 == d2); + // -Y + Circuit c3(1, 1); + c3.add_op(OpType::Vdg, {0}); + c3.add_op(OpType::Measure, {0, 0}); + c3.add_op(OpType::T, {0}); + Circuit d3(1, 1); + d3.add_op(OpType::Vdg, {0}); + d3.add_op(OpType::Measure, {0, 0}); + d3.add_op(OpType::V, {0}); + d3.add_op(OpType::Ry, 3.75, {0}); + d3.add_op(OpType::V, {0}); + d3.add_op(OpType::X, {0}); + // V;Ry(3.75);V;X = T + REQUIRE(Transforms::greedy_pauli_optimisation().apply(c3)); + REQUIRE(c3 == d3); + // -Z + Circuit c4(1, 1); + c4.add_op(OpType::X, {0}); + c4.add_op(OpType::Measure, {0, 0}); + c4.add_op(OpType::T, {0}); + Circuit d4(1, 1); + d4.add_op(OpType::X, {0}); + d4.add_op(OpType::Measure, {0, 0}); + d4.add_op(OpType::X, {0}); + d4.add_op(OpType::Rz, 3.75, {0}); + d4.add_op(OpType::X, {0}); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(c4)); + REQUIRE(c4 == d4); + } + + GIVEN("Circuit with resets") { + Circuit circ(2); + circ.add_op(OpType::CX, {0, 1}); + circ.add_op(OpType::Reset, {0}); + Circuit d(circ); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(circ)); + REQUIRE(circ == d); + } + + GIVEN("Circuit with resets 2") { + // -X/Z + Circuit c1(1); + c1.add_op(OpType::Z, {0}); + c1.add_op(OpType::H, {0}); + c1.add_op(OpType::Reset, {0}); + Circuit d1(1); + d1.add_op(OpType::H, {0}); + d1.add_op(OpType::X, {0}); + d1.add_op(OpType::Reset, {0}); + d1.add_op(OpType::X, {0}); + d1.add_op(OpType::H, {0}); + d1.add_op(OpType::H, {0}); + d1.add_op(OpType::X, {0}); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(c1)); + REQUIRE(c1 == d1); + + // X/-Z + Circuit c2(1); + c2.add_op(OpType::X, {0}); + c2.add_op(OpType::H, {0}); + c2.add_op(OpType::Reset, {0}); + Circuit d2(1); + d2.add_op(OpType::H, {0}); + d2.add_op(OpType::Z, {0}); + d2.add_op(OpType::Reset, {0}); + d2.add_op(OpType::Z, {0}); + d2.add_op(OpType::H, {0}); + d2.add_op(OpType::H, {0}); + d2.add_op(OpType::Z, {0}); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(c2)); + REQUIRE(c2 == d2); + } + + GIVEN("Circuit with measures, classicals, and resets") { + Circuit circ(3, 1); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::Z, Pauli::Z}, 0.3)), + {0, 1, 2}); + circ.add_op(OpType::Measure, {0, 0}); + circ.add_op(ClassicalX(), {0}); + circ.add_op(OpType::Reset, {1}); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(circ)); + REQUIRE(circ.count_n_qubit_gates(2) == 4); + REQUIRE(circ.count_gates(OpType::ClassicalTransform) == 1); + REQUIRE(circ.count_gates(OpType::Measure) == 1); + REQUIRE(circ.count_gates(OpType::Reset) == 1); + } + GIVEN("Compile to ZZPhase") { + Circuit circ(2); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::X}, 0.3)), {0, 1}); + Circuit d1(circ); + Circuit d2(circ); + REQUIRE(Transforms::greedy_pauli_optimisation(0.7, 0.3, 500, 500, 0, true) + .apply(d1)); + REQUIRE(Transforms::greedy_pauli_optimisation(0.7, 0.3, 500, 500, 0, false) + .apply(d2)); + REQUIRE(test_unitary_comparison(circ, d1, true)); + REQUIRE(test_unitary_comparison(circ, d2, true)); + REQUIRE(d1.count_n_qubit_gates(2) == 1); + REQUIRE(d2.count_n_qubit_gates(2) == 2); + } + GIVEN("Multiple ZZPhases at once") { + Circuit circ(6); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::X}, 0.3)), {0, 1}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Z, Pauli::X}, 0.1)), {2, 3}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::Y}, 0.2)), {4, 5}); + Circuit d(circ); + REQUIRE(Transforms::greedy_pauli_optimisation(0.7, 0.3, 500, 500, 0, true) + .apply(d)); + REQUIRE(test_unitary_comparison(circ, d, true)); + REQUIRE(d.count_n_qubit_gates(2) == 3); + } + GIVEN("Large circuit with ZZPhase") { + Circuit circ(6); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::X}, 0.3)), {0, 1}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Z, Pauli::Y, Pauli::X}, 0.2)), + {0, 1, 2}); + circ.add_box( + PauliExpCommutingSetBox({ + {{Pauli::I, Pauli::Y, Pauli::I, Pauli::Z}, 1.2}, + {{Pauli::X, Pauli::Y, Pauli::Z, Pauli::I}, 0.8}, + {{Pauli::I, Pauli::I, Pauli::I, Pauli::Z}, 1.25}, + }), + {1, 2, 3, 4}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Y, Pauli::X}, 0.1)), {2, 3}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Z, Pauli::Y, Pauli::X}, 0.11)), + {1, 3, 4}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Y, Pauli::Y}, 0.2)), {4, 5}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Z, Pauli::Z, Pauli::X}, 0.15)), + {2, 4, 5}); + circ.add_box( + PauliExpBox( + SymPauliTensor({Pauli::X, Pauli::X, Pauli::X, Pauli::X}, 0.25)), + {2, 4, 5, 0}); + circ.add_box( + PauliExpBox( + SymPauliTensor({Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, 0.125)), + {1, 3, 5, 0}); + Circuit d(circ); + REQUIRE(Transforms::greedy_pauli_optimisation(0.7, 0.3, 500, 500, 0, true) + .apply(d)); + REQUIRE(test_unitary_comparison(circ, d, true)); + } + GIVEN("Select TQE over ZZPhase") { + Circuit circ(3); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::Y}, 0.3)), {0, 1}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Z, Pauli::Y, Pauli::Z}, 0.22)), + {0, 1, 2}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Z, Pauli::Y, Pauli::X}, 0.15)), + {0, 1, 2}); + Circuit d(circ); + REQUIRE(Transforms::greedy_pauli_optimisation(0.7, 0.3, 500, 500, 0, true) + .apply(d)); + REQUIRE(test_unitary_comparison(circ, d, true)); + // if the first XY was implemented using a ZZPhase + // then 2 TQEs is needed to conjugate the remaining two strings to weight 2 + // hence 5 2-qubit gates in total. + REQUIRE(d.count_n_qubit_gates(2) == 4); + } +} + +SCENARIO("Test GreedyPauliSimp for individual gates") { + Circuit circ(1); + circ.add_op(OpType::Z, {0}); + std::vector ops_0q = { + get_op_ptr(OpType::Phase, 0.25), + }; + std::vector ops_1q = { + get_op_ptr(OpType::noop), + get_op_ptr(OpType::Z), + get_op_ptr(OpType::X), + get_op_ptr(OpType::Y), + get_op_ptr(OpType::S), + get_op_ptr(OpType::V), + get_op_ptr(OpType::Sdg), + get_op_ptr(OpType::Vdg), + get_op_ptr(OpType::H), + get_op_ptr(OpType::Rz, 0.25), + get_op_ptr(OpType::Rz, 0.5), + get_op_ptr(OpType::Rx, 1), + get_op_ptr(OpType::Rx, 0.15), + get_op_ptr(OpType::Ry, 0.25), + get_op_ptr(OpType::Ry, -0.5), + get_op_ptr(OpType::PhasedX, {0.15, 0.2}), + get_op_ptr(OpType::PhasedX, {0.5, -0.5}), + get_op_ptr(OpType::PhasedX, {0.2, 1}), + get_op_ptr(OpType::T), + get_op_ptr(OpType::Tdg), + }; + std::vector ops_2q = { + get_op_ptr(OpType::SWAP), + get_op_ptr(OpType::CX), + get_op_ptr(OpType::CY), + get_op_ptr(OpType::CZ), + get_op_ptr(OpType::ZZMax), + get_op_ptr(OpType::ZZPhase, 0.25), + get_op_ptr(OpType::ZZPhase, 0.5), + get_op_ptr(OpType::PhaseGadget, 0.5, 2), + get_op_ptr(OpType::XXPhase, 0.25), + get_op_ptr(OpType::XXPhase, 0.5), + get_op_ptr(OpType::YYPhase, 0.25), + get_op_ptr(OpType::YYPhase, 1), + }; + for (Op_ptr op : ops_0q) { + Circuit circ(1); + circ.add_op(op, {}); + Circuit d(circ); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(circ)); + REQUIRE(test_unitary_comparison(circ, d, true)); + } + for (Op_ptr op : ops_1q) { + Circuit circ(1); + circ.add_op(op, {0}); + Circuit d(circ); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(circ)); + REQUIRE(test_unitary_comparison(circ, d, true)); + } + for (Op_ptr op : ops_2q) { + Circuit circ(2); + circ.add_op(op, {0, 1}); + Circuit d(circ); + REQUIRE(Transforms::greedy_pauli_optimisation().apply(circ)); + REQUIRE(test_unitary_comparison(circ, d, true)); + } } SCENARIO("Test GreedyPauliSimp pass construction") { // test pass construction diff --git a/tket/test/src/test_PauliGraph.cpp b/tket/test/src/test_PauliGraph.cpp index 7ddd257586..c51cac23d3 100644 --- a/tket/test/src/test_PauliGraph.cpp +++ b/tket/test/src/test_PauliGraph.cpp @@ -28,6 +28,7 @@ #include "tket/PauliGraph/PauliGraph.hpp" #include "tket/Transformations/PauliOptimisation.hpp" #include "tket/Transformations/Rebase.hpp" +#include "tket/Utils/UnitID.hpp" namespace tket { namespace test_PauliGraph { @@ -693,6 +694,14 @@ SCENARIO("Test mutual diagonalisation of fully commuting sets") { test2.symbol_substitution(symbol_map); REQUIRE(test_statevector_comparison(test1, test2)); } + GIVEN("A circuit with a classical bit") { + // https://github.com/CQCL/tket/issues/1578 + Circuit c0(0, 1); + CircBox cbox(c0); + Circuit c(0, 1); + c.add_box(cbox, {Bit("c", 0)}); + REQUIRE_NOTHROW(Transforms::special_UCC_synthesis().apply(c)); + } GIVEN( "Clifford merges requires removing from start line without segfault " "(Grover circuit)") { From 9718f179a190a64d3ae27c71b0e3d365d113bbf3 Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:19:00 +0000 Subject: [PATCH 13/15] Document ClExpr and friends. (#1658) (#1674) Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- pytket/binders/circuit/clexpr.cpp | 54 ++++++++++++++++++++----------- pytket/docs/circuit.rst | 19 ++++++++++- pytket/docs/circuit_class.rst | 2 ++ pytket/pytket/_tket/circuit.pyi | 42 +++++++++++++++--------- 4 files changed, 82 insertions(+), 35 deletions(-) diff --git a/pytket/binders/circuit/clexpr.cpp b/pytket/binders/circuit/clexpr.cpp index f45559f585..fa2f73c227 100644 --- a/pytket/binders/circuit/clexpr.cpp +++ b/pytket/binders/circuit/clexpr.cpp @@ -423,7 +423,9 @@ void init_clexpr(py::module &m) { py::class_>( m, "ClBitVar", "A bit variable within an expression") .def( - py::init(), "Construct from an integer identifier", + py::init(), + "Construct from an integer identifier.\n\n" + ":param i: integer identifier for the variable", py::arg("i")) .def("__eq__", &py_equals) .def( @@ -443,12 +445,14 @@ void init_clexpr(py::module &m) { .def("__hash__", [](const ClBitVar &var) { return var.index; }) .def_property_readonly( "index", [](const ClBitVar &var) { return var.index; }, - ":return: integer identifier for the variable"); + "integer identifier for the variable"); py::class_>( m, "ClRegVar", "A register variable within an expression") .def( - py::init(), "Construct from an integer identifier", + py::init(), + "Construct from an integer identifier.\n\n" + ":param i: integer identifier for the variable", py::arg("i")) .def("__eq__", &py_equals) .def( @@ -468,14 +472,18 @@ void init_clexpr(py::module &m) { .def("__hash__", [](const ClRegVar &var) { return var.index; }) .def_property_readonly( "index", [](const ClRegVar &var) { return var.index; }, - ":return: integer identifier for the variable"); + "integer identifier for the variable"); py::class_>( m, "ClExpr", "A classical expression") .def( py::init>(), - "Construct from an operation and a list of arguments", py::arg("op"), - py::arg("args")) + "Construct from an operation type and a list of arguments.\n\n" + ":param op: the operation type\n" + ":param args: list of arguments to the expression (which may be " + "integers, :py:class:`ClBitVar` variables, :py:class:`ClRegVar` " + "variables, or other :py:class:`ClExpr`)", + py::arg("op"), py::arg("args")) .def("__eq__", &py_equals) .def( "__str__", @@ -485,8 +493,8 @@ void init_clexpr(py::module &m) { return ss.str(); }) .def("__hash__", &deletedHash, deletedHashDocstring) - .def_property_readonly("op", &ClExpr::get_op, ":return: main operation") - .def_property_readonly("args", &ClExpr::get_args, ":return: arguments") + .def_property_readonly("op", &ClExpr::get_op, "main operation") + .def_property_readonly("args", &ClExpr::get_args, "arguments") .def( "as_qasm", [](const ClExpr &expr, const std::map input_bits, @@ -499,13 +507,24 @@ void init_clexpr(py::module &m) { py::class_>( m, "WiredClExpr", - "A classical expression defined over a sequence of bits") + "An operation defined by a classical expression over a sequence of bits") .def( py::init< ClExpr, std::map, std::map>, std::vector>(), - "Construct from an expression with bit and register positions", + "Construct from an expression with bit and register positions.\n\n" + ":param expr: an abstract classical expression\n" + ":param bit_posn: a map whose keys are the indices of the " + ":py:class:`ClBitVar` occurring in the expression, and whose values " + "are the positions of the corresponding bits in the arguments of the " + "operation\n" + ":param reg_posn: a map whose keys are the indices of the " + ":py:class:`ClRegVar` occurring in the expression, and whose values " + "are the sequences of positions of the corresponding bits in the " + "arguments of the operation\n" + ":param output_posn: a list giving the positions of the output bits " + "in the arguments of the operation", py::arg("expr"), py::arg("bit_posn") = std::map(), py::arg("reg_posn") = std::map>(), py::arg("output_posn") = std::vector()) @@ -518,15 +537,13 @@ void init_clexpr(py::module &m) { return ss.str(); }) .def("__hash__", &deletedHash, deletedHashDocstring) + .def_property_readonly("expr", &WiredClExpr::get_expr, "expression") .def_property_readonly( - "expr", &WiredClExpr::get_expr, ":return: expression") + "bit_posn", &WiredClExpr::get_bit_posn, "bit positions") .def_property_readonly( - "bit_posn", &WiredClExpr::get_bit_posn, ":return: bit positions") + "reg_posn", &WiredClExpr::get_reg_posn, "register positions") .def_property_readonly( - "reg_posn", &WiredClExpr::get_reg_posn, ":return: register positions") - .def_property_readonly( - "output_posn", &WiredClExpr::get_output_posn, - ":return: output positions") + "output_posn", &WiredClExpr::get_output_posn, "output positions") .def( "to_dict", [](const WiredClExpr &wexpr) { @@ -545,10 +562,9 @@ void init_clexpr(py::module &m) { .def( py::init(), "Construct from a wired classical expression") + .def_property_readonly("type", &ClExprOp::get_type, "operation type") .def_property_readonly( - "type", &ClExprOp::get_type, ":return: operation type") - .def_property_readonly( - "expr", &ClExprOp::get_wired_expr, ":return: wired expression"); + "expr", &ClExprOp::get_wired_expr, "wired expression"); } } // namespace tket diff --git a/pytket/docs/circuit.rst b/pytket/docs/circuit.rst index fe50f0d1a0..40b51296d0 100644 --- a/pytket/docs/circuit.rst +++ b/pytket/docs/circuit.rst @@ -65,6 +65,23 @@ pytket.circuit :members: .. autoclass:: pytket.circuit.ClassicalExpBox :members: +.. autoclass:: pytket.circuit.ClExprOp + :special-members: + :members: +.. autoclass:: pytket.circuit.WiredClExpr + :special-members: __init__ + :members: +.. autoclass:: pytket.circuit.ClExpr + :special-members: __init__ + :members: +.. autoclass:: pytket.circuit.ClOp + :members: +.. autoclass:: pytket.circuit.ClBitVar + :special-members: __init__ + :members: +.. autoclass:: pytket.circuit.ClRegVar + :special-members: __init__ + :members: .. autoclass:: pytket.circuit.PhasePolyBox :special-members: :members: @@ -124,4 +141,4 @@ pytket.circuit :members: .. autoclass:: pytket.circuit.DummyBox :special-members: - :members: \ No newline at end of file + :members: diff --git a/pytket/docs/circuit_class.rst b/pytket/docs/circuit_class.rst index b9208d16f9..cb7bf44224 100644 --- a/pytket/docs/circuit_class.rst +++ b/pytket/docs/circuit_class.rst @@ -182,6 +182,8 @@ condition on a specified set of bit values.) .. automethod:: add_classicalexpbox_register + .. automethod:: add_clexpr + .. automethod:: qubit_create .. automethod:: qubit_create_all diff --git a/pytket/pytket/_tket/circuit.pyi b/pytket/pytket/_tket/circuit.pyi index b89daa701e..4cbc158def 100644 --- a/pytket/pytket/_tket/circuit.pyi +++ b/pytket/pytket/_tket/circuit.pyi @@ -2561,7 +2561,9 @@ class ClBitVar: ... def __init__(self, i: int) -> None: """ - Construct from an integer identifier + Construct from an integer identifier. + + :param i: integer identifier for the variable """ def __repr__(self) -> str: ... @@ -2570,7 +2572,7 @@ class ClBitVar: @property def index(self) -> int: """ - :return: integer identifier for the variable + integer identifier for the variable """ class ClExpr: """ @@ -2587,7 +2589,10 @@ class ClExpr: """ def __init__(self, op: ClOp, args: list[int | ClBitVar | ClRegVar | ClExpr]) -> None: """ - Construct from an operation and a list of arguments + Construct from an operation type and a list of arguments. + + :param op: the operation type + :param args: list of arguments to the expression (which may be integers, :py:class:`ClBitVar` variables, :py:class:`ClRegVar` variables, or other :py:class:`ClExpr`) """ def __str__(self) -> str: ... @@ -2598,12 +2603,12 @@ class ClExpr: @property def args(self) -> list[int | ClBitVar | ClRegVar | ClExpr]: """ - :return: arguments + arguments """ @property def op(self) -> ClOp: """ - :return: main operation + main operation """ class ClExprOp(Op): """ @@ -2619,12 +2624,12 @@ class ClExprOp(Op): @property def expr(self) -> WiredClExpr: """ - :return: wired expression + wired expression """ @property def type(self) -> OpType: """ - :return: operation type + operation type """ class ClOp: """ @@ -2770,7 +2775,9 @@ class ClRegVar: ... def __init__(self, i: int) -> None: """ - Construct from an integer identifier + Construct from an integer identifier. + + :param i: integer identifier for the variable """ def __repr__(self) -> str: ... @@ -2779,7 +2786,7 @@ class ClRegVar: @property def index(self) -> int: """ - :return: integer identifier for the variable + integer identifier for the variable """ class ClassicalEvalOp(ClassicalOp): """ @@ -4389,7 +4396,7 @@ class WASMOp(ClassicalOp): """ class WiredClExpr: """ - A classical expression defined over a sequence of bits + An operation defined by a classical expression over a sequence of bits """ @staticmethod def _pybind11_conduit_v1_(*args, **kwargs): # type: ignore @@ -4407,7 +4414,12 @@ class WiredClExpr: """ def __init__(self, expr: ClExpr, bit_posn: dict[int, int] = {}, reg_posn: dict[int, list[int]] = {}, output_posn: list[int] = []) -> None: """ - Construct from an expression with bit and register positions + Construct from an expression with bit and register positions. + + :param expr: an abstract classical expression + :param bit_posn: a map whose keys are the indices of the :py:class:`ClBitVar` occurring in the expression, and whose values are the positions of the corresponding bits in the arguments of the operation + :param reg_posn: a map whose keys are the indices of the :py:class:`ClRegVar` occurring in the expression, and whose values are the sequences of positions of the corresponding bits in the arguments of the operation + :param output_posn: a list giving the positions of the output bits in the arguments of the operation """ def __str__(self) -> str: ... @@ -4418,22 +4430,22 @@ class WiredClExpr: @property def bit_posn(self) -> dict[int, int]: """ - :return: bit positions + bit positions """ @property def expr(self) -> ClExpr: """ - :return: expression + expression """ @property def output_posn(self) -> list[int]: """ - :return: output positions + output positions """ @property def reg_posn(self) -> dict[int, list[int]]: """ - :return: register positions + register positions """ def fresh_symbol(preferred: str = 'a') -> sympy.Symbol: """ From c365f8fde0ab050afc8f3c91ca9e768cb55f5d77 Mon Sep 17 00:00:00 2001 From: Callum Macpherson <93673602+CalMacCQ@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:20:59 +0000 Subject: [PATCH 14/15] docs: link to pytket-quest extensions docs (#1648) (#1649) * add pytket-quest link to sidebar * add AerDensityMatrixBackend * add quest to list of SV simulators * fix bad link syntax --- pytket/docs/extensions.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pytket/docs/extensions.md b/pytket/docs/extensions.md index 73f9535bb2..08b70e28f5 100644 --- a/pytket/docs/extensions.md +++ b/pytket/docs/extensions.md @@ -69,12 +69,16 @@ A full list of available pytket backends is shown below. [ProjectQBackend](inv:#*.ProjectQBackend) \- Backend for running statevector simulations on the ProjectQ simulator. +[QuESTBackend](https://docs.quantinuum.com/tket/extensions/pytket-quest/api.html#pytket.extensions.quest.QuESTBackend) Interface to the [QUEST simulator](https://quest.qtechtheory.org/docs/). + ## Unitary Simulators [AerUnitaryBackend](inv:#*.AerUnitaryBackend) - Backend for running simulations on the Qiskit Aer unitary simulator. ## Density Matrix Simulators +[AerDensityMatrixBackend](inv:#*qiskit.AerDensityMatrixBackend) - Backend for density matrix simulation using qiskit Aer. Can take a `NoiseModel` as an optional argument. + [CirqDensityMatrixSampleBackend](inv:#*.CirqDensityMatrixSampleBackend) \- Backend for Cirq density matrix simulator sampling. @@ -123,6 +127,7 @@ pytket-pyzx pytket-qir pytket-qiskit pytket-quantinuum +pytket-quest pytket-cutensornet pytket-qulacs pytket-qujax From 3d7685b7983926e79d19cdb65547196a1f54034b Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:14:27 +0000 Subject: [PATCH 15/15] Update `docmain` to align with `main` (#1688) --- .github/workflows/build-without-conan.yml | 6 +- .github/workflows/build_and_test.yml | 2 +- .github/workflows/lint.yml | 8 +- .github/workflows/pytket_benchmarking.yml | 10 +- build-without-conan.md | 6 +- pytket/binders/circuit/Circuit/add_op.cpp | 47 +- pytket/binders/circuit/Circuit/main.cpp | 20 +- pytket/binders/circuit/clexpr.cpp | 4 +- pytket/binders/circuit/main.cpp | 4 +- pytket/binders/passes.cpp | 83 ++- pytket/binders/transform.cpp | 21 +- pytket/conanfile.py | 6 +- pytket/docs/README.md | 22 +- pytket/docs/changelog.rst | 38 ++ pytket/docs/check_circuit_class_docs.py | 2 +- pytket/pytket/__init__.py | 12 +- pytket/pytket/_tket/circuit.pyi | 28 +- pytket/pytket/_tket/passes.pyi | 36 +- pytket/pytket/_tket/transform.pyi | 6 +- pytket/pytket/backends/__init__.py | 2 +- pytket/pytket/backends/backend.py | 80 ++- pytket/pytket/backends/backend_exceptions.py | 11 +- pytket/pytket/backends/backendinfo.py | 88 ++-- pytket/pytket/backends/backendresult.py | 133 +++-- pytket/pytket/backends/resulthandle.py | 14 +- pytket/pytket/backends/status.py | 31 +- pytket/pytket/circuit/__init__.py | 29 +- pytket/pytket/circuit/add_condition.py | 30 +- pytket/pytket/circuit/clexpr.py | 43 +- pytket/pytket/circuit/decompose_classical.py | 218 ++++++-- pytket/pytket/circuit/display/__init__.py | 9 +- pytket/pytket/circuit/logic_exp.py | 122 ++--- pytket/pytket/circuit/named_types.py | 5 +- pytket/pytket/circuit_library/__init__.py | 152 +++--- pytket/pytket/config/__init__.py | 2 +- pytket/pytket/config/pytket_config.py | 24 +- pytket/pytket/passes/__init__.py | 2 +- pytket/pytket/passes/auto_rebase.py | 10 +- pytket/pytket/passes/passselector.py | 10 +- pytket/pytket/passes/resizeregpass.py | 3 +- pytket/pytket/passes/script.py | 113 +++-- pytket/pytket/qasm/__init__.py | 8 +- pytket/pytket/qasm/grammar.py | 2 +- pytket/pytket/qasm/qasm.py | 478 ++++++++++-------- pytket/pytket/quipper/quipper.py | 134 ++--- pytket/pytket/transform/__init__.py | 2 +- pytket/pytket/unit_id/__init__.py | 17 +- pytket/pytket/utils/__init__.py | 34 +- pytket/pytket/utils/distribution.py | 38 +- pytket/pytket/utils/expectations.py | 134 +++-- pytket/pytket/utils/graph.py | 9 +- pytket/pytket/utils/measurements.py | 6 +- pytket/pytket/utils/operators.py | 56 +- pytket/pytket/utils/outcomearray.py | 16 +- pytket/pytket/utils/prepare.py | 5 +- pytket/pytket/utils/results.py | 36 +- pytket/pytket/utils/spam.py | 76 ++- pytket/pytket/utils/stats.py | 3 +- pytket/pytket/utils/symbolic.py | 27 +- pytket/pytket/utils/term_sequence.py | 9 +- pytket/pytket/wasm/wasm.py | 24 +- pytket/pytket/zx/tensor_eval.py | 56 +- pytket/ruff.toml | 81 +++ pytket/setup.py | 30 +- pytket/tests/add_circuit_test.py | 1 - pytket/tests/ansatz_sequence_test.py | 17 +- .../architecture_aware_synthesis_test.py | 2 +- pytket/tests/architecture_test.py | 18 +- pytket/tests/assertion_test.py | 10 +- pytket/tests/backend_test.py | 30 +- pytket/tests/backendinfo_test.py | 23 +- pytket/tests/boxes/phase_poly_box_test.py | 20 +- pytket/tests/characterisation_test.py | 4 +- pytket/tests/circuit_test.py | 147 +++--- pytket/tests/classical_test.py | 274 ++-------- pytket/tests/clexpr_test.py | 52 +- pytket/tests/compilation_test.py | 16 +- pytket/tests/distribution_test.py | 8 +- pytket/tests/logic_exp_test.py | 14 +- pytket/tests/mapping_test.py | 27 +- pytket/tests/measurement_setup_test.py | 10 +- pytket/tests/mitigation_test.py | 29 +- pytket/tests/passes_script_test.py | 10 +- pytket/tests/passes_serialisation_test.py | 68 ++- pytket/tests/placement_test.py | 31 +- pytket/tests/predicates_test.py | 177 ++++--- pytket/tests/pytket_config_test.py | 21 +- pytket/tests/qasm_test.py | 175 +++++-- pytket/tests/qasm_test_files/longreg.qasm | 83 +++ .../tests/qasm_test_files/test18_output.qasm | 16 +- pytket/tests/qubitpaulioperator_test.py | 16 +- pytket/tests/quipper_test.py | 8 +- pytket/tests/simulator/__init__.py | 2 +- pytket/tests/simulator/tket_sim_backend.py | 61 ++- pytket/tests/simulator/tket_sim_wrapper.py | 6 +- pytket/tests/strategies.py | 23 +- pytket/tests/tableau_test.py | 6 +- pytket/tests/tket_sim_test.py | 16 +- pytket/tests/transform_test.py | 83 ++- pytket/tests/unit_id/copy_test.py | 2 +- pytket/tests/utils_test.py | 42 +- pytket/tests/zx_diagram_test.py | 39 +- schemas/circuit_v1.json | 158 ++++++ schemas/compiler_pass_v1.json | 24 +- tket/conanfile.py | 4 +- tket/include/tket/Circuit/Boxes.hpp | 4 + tket/include/tket/Circuit/Circuit.hpp | 19 +- tket/include/tket/Ops/ClExpr.hpp | 3 +- tket/include/tket/Predicates/CompilerPass.hpp | 10 +- .../tket/Predicates/PassGenerators.hpp | 10 +- .../GreedyPauliOptimisation.hpp | 28 +- tket/src/Circuit/Boxes.cpp | 16 + tket/src/Circuit/CircPool.cpp | 12 - tket/src/Circuit/Circuit.cpp | 2 +- tket/src/Circuit/basic_circ_manip.cpp | 4 +- tket/src/Circuit/macro_circ_info.cpp | 10 +- tket/src/OpType/OpTypeFunctions.cpp | 7 +- tket/src/Ops/ClExpr.cpp | 7 +- tket/src/Predicates/CompilerPass.cpp | 51 +- tket/src/Predicates/PassGenerators.cpp | 42 +- .../GreedyPauliOptimisation.cpp | 127 ++++- tket/test/src/Circuit/test_Circ.cpp | 50 ++ tket/test/src/test_ClExpr.cpp | 11 +- tket/test/src/test_GreedyPauli.cpp | 113 ++++- tket/test/src/test_json.cpp | 48 +- 125 files changed, 3093 insertions(+), 2027 deletions(-) mode change 100755 => 100644 pytket/pytket/__init__.py create mode 100644 pytket/ruff.toml mode change 100755 => 100644 pytket/setup.py create mode 100644 pytket/tests/qasm_test_files/longreg.qasm diff --git a/.github/workflows/build-without-conan.yml b/.github/workflows/build-without-conan.yml index c31470cc7a..9544d442a3 100644 --- a/.github/workflows/build-without-conan.yml +++ b/.github/workflows/build-without-conan.yml @@ -40,9 +40,9 @@ jobs: - name: Install symengine run: | cd ${TMP_DIR} - wget https://github.com/symengine/symengine/archive/refs/tags/v0.12.0.tar.gz - tar xzvf v0.12.0.tar.gz - cd symengine-0.12.0/ + wget https://github.com/symengine/symengine/archive/refs/tags/v0.13.0.tar.gz + tar xzvf v0.13.0.tar.gz + cd symengine-0.13.0/ mkdir build cd build cmake -GNinja -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DBUILD_TESTS=OFF -DBUILD_BENCHMARKS=OFF .. diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 5ada774af1..a870b45a61 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -428,7 +428,7 @@ jobs: python -m pip install -U mypy pybind11-stubgen cd pytket ./stub_generation/regenerate_stubs - git diff --quiet pytket/_tket && echo "Stubs are up-to-date" || exit 1 # fail if stubs change after regeneration + git diff pytket/_tket && echo "Stubs are up-to-date" || exit 1 # fail if stubs change after regeneration python -m mypy --config-file=mypy.ini --no-incremental -p pytket -p tests - name: Upload package if: github.event_name == 'push' && needs.check_changes.outputs.tket_changed == 'true' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2d2c8f7225..15f6514f5c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,8 +15,8 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.x' - - name: Install black and pylint - run: pip install black pylint + - name: Install black, pylint and ruff + run: pip install black pylint ruff - name: Check files are formatted with black run: | # Paths specified to avoid formatting submodules @@ -25,3 +25,7 @@ jobs: run: | cd pytket pylint --ignore-paths=pytket/_tket pytket/ + - name: ruff check + run: | + cd pytket + ruff check . diff --git a/.github/workflows/pytket_benchmarking.yml b/.github/workflows/pytket_benchmarking.yml index 001418b4ac..c9c9293cbf 100644 --- a/.github/workflows/pytket_benchmarking.yml +++ b/.github/workflows/pytket_benchmarking.yml @@ -100,10 +100,12 @@ jobs: - name: Post to a Slack channel id: slack - uses: slackapi/slack-github-action@v1.27.0 + uses: slackapi/slack-github-action@v2.0.0 with: - channel-id: 'G01CP0YFFA7' - slack-message: '${{ env.RETURN_TEST }} Release tag: ${{ github.event.release.tag_name }}.' + method: chat.postMessage + token: ${{ secrets.PYTKET_BENCHMARKING_SLACK_BOT_TOKEN }} + payload: | + channel: 'G01CP0YFFA7' + text: '${{ env.RETURN_TEST }} Release tag: ${{ github.event.release.tag_name }}.' env: - SLACK_BOT_TOKEN: ${{ secrets.PYTKET_BENCHMARKING_SLACK_BOT_TOKEN }} RETURN_TEST: ${{ env.RETURN_TEST }} diff --git a/build-without-conan.md b/build-without-conan.md index 9a91f1f50f..4663f65ed1 100644 --- a/build-without-conan.md +++ b/build-without-conan.md @@ -48,9 +48,9 @@ make install ``` cd ${TMP_DIR} -wget https://github.com/symengine/symengine/archive/refs/tags/v0.12.0.tar.gz -tar xzvf v0.12.0.tar.gz -cd symengine-0.12.0/ +wget https://github.com/symengine/symengine/archive/refs/tags/v0.13.0.tar.gz +tar xzvf v0.13.0.tar.gz +cd symengine-0.13.0/ mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DBUILD_TESTS=OFF -DBUILD_BENCHMARKS=OFF .. diff --git a/pytket/binders/circuit/Circuit/add_op.cpp b/pytket/binders/circuit/Circuit/add_op.cpp index c8c952d2f3..07cccadb2a 100644 --- a/pytket/binders/circuit/Circuit/add_op.cpp +++ b/pytket/binders/circuit/Circuit/add_op.cpp @@ -414,6 +414,11 @@ void init_circuit_add_op(py::class_> &c) { [](Circuit *circ, const py::tket_custom::BitLogicExpression &exp, const py::tket_custom::SequenceVec &outputs, const py::kwargs &kwargs) { + PyErr_WarnEx( + PyExc_DeprecationWarning, + "The add_classicalexpbox_bit method is deprecated. Please use " + "Circuit::add_clexpr() instead.", + 1); auto inputs = exp.attr("all_inputs")().cast>(); py::tket_custom::SequenceVec o_vec, io_vec; @@ -439,7 +444,9 @@ void init_circuit_add_op(py::class_> &c) { n_i, n_io, n_o, exp), o_vec, kwargs); }, - "Append a :py:class:`ClassicalExpBox` over Bit to the circuit.\n\n" + "Append a :py:class:`ClassicalExpBox` over Bit to the circuit.\n" + "DEPRECATED: Please use :py:meth:`add_clexpr` instead. This method " + "will be removed after pytket 1.40.\n\n" ":param classicalexpbox: The box to append\n" ":param args: Indices of the qubits to append the box to" "\n:return: the new :py:class:`Circuit`", @@ -450,6 +457,11 @@ void init_circuit_add_op(py::class_> &c) { const py::tket_custom::BitRegisterLogicExpression &exp, const py::tket_custom::SequenceVec &outputs, const py::kwargs &kwargs) { + PyErr_WarnEx( + PyExc_DeprecationWarning, + "The add_classicalexpbox_register method is deprecated. Please " + "use Circuit::add_clexpr() instead.", + 1); auto inputs = exp.attr("all_inputs")().cast>(); std::set all_bits; @@ -481,7 +493,9 @@ void init_circuit_add_op(py::class_> &c) { o_vec, kwargs); }, "Append a :py:class:`ClassicalExpBox` over BitRegister to the " - "circuit.\n\n" + "circuit.\n" + "DEPRECATED: Please use :py:meth:`add_clexpr` instead. This method " + "will be removed after pytket 1.40.\n\n" ":param classicalexpbox: The box to append\n" ":param args: Indices of the qubits to append the box to" "\n:return: the new :py:class:`Circuit`", @@ -499,6 +513,35 @@ void init_circuit_add_op(py::class_> &c) { ":param args: The bits to apply the expression to\n" ":return: the new :py:class:`Circuit`", py::arg("expr"), py::arg("args")) + .def( + "add_clexpr_from_logicexp", + [](Circuit *circ, const py::tket_custom::LogicExpression &exp, + const py::tket_custom::SequenceVec &output_bits, + const py::kwargs &kwargs) { + py::list outputs; + for (const auto &bit : output_bits) { + outputs.append(bit); + } + py::module clexpr = py::module::import("pytket.circuit.clexpr"); + py::object add_op = + clexpr.attr("_add_clexpr_to_circuit_from_logicexp"); + add_op(circ, exp, outputs, **kwargs); + return circ; + }, + "Append a :py:class:`ClExprOp` defined in terms of a logical " + "expression.\n\n" + "Example:\n" + ">>> c = Circuit()\n" + ">>> x_reg = c.add_c_register('x', 3)\n" + ">>> y_reg = c.add_c_register('y', 3)\n" + ">>> z_reg = c.add_c_register('z', 3)\n" + ">>> c.add_clexpr_from_logicexp(x_reg | y_reg, z_reg.to_list())\n" + ">>> [ClExpr x[0], x[1], x[2], y[0], y[1], y[2], z[0], z[1], z[2]; " + "]\n\n" + ":param exp: logical expression\n" + ":param output_bits: list of bits in output\n" + ":return: the updated circuit", + py::arg("exp"), py::arg("output_bits")) .def( "add_custom_gate", [](Circuit *circ, const composite_def_ptr_t &definition, diff --git a/pytket/binders/circuit/Circuit/main.cpp b/pytket/binders/circuit/Circuit/main.cpp index 867996beb1..38c6f7c2bb 100644 --- a/pytket/binders/circuit/Circuit/main.cpp +++ b/pytket/binders/circuit/Circuit/main.cpp @@ -322,7 +322,12 @@ void def_circuit(py::class_> &pyCircuit) { .def( "flatten_registers", &Circuit::flatten_registers, "Combines all qubits into a single register namespace with " - "the default name, and likewise for bits") + "the default name, and likewise for bits" + "\n\n:param relabel_classical_expression: Determines whether python " + "classical expressions held in `ClassicalExpBox` have their " + "expression " + "relabelled to match relabelled Bit.", + py::arg("relabel_classical_expression") = true) // Circuit composition: .def( @@ -472,15 +477,18 @@ void def_circuit(py::class_> &pyCircuit) { "\n\n:return: the circuit depth") .def( "n_gates_of_type", - [](const Circuit &circ, const OpType &_type) { - return circ.count_gates(_type); + [](const Circuit &circ, const OpType &_type, + const bool include_conditional) { + return circ.count_gates(_type, include_conditional); }, "Returns the number of vertices in the dag of a given " "operation type.\n\n>>> c.CX(0,1)\n>>> c.H(0)\n>>> " "c.CX(0,1)\n>>> c.n_gates_of_type(OpType.CX)\n2\n\n:param " - "type: The operation type to search for\n:return: the " - "number of operations matching `type`", - py::arg("type")) + "type: The operation type to search for\n\n:param " + "include_conditional: if set to true, conditional gates will " + "be counted, too\n\n:return: the number of operations " + "matching `type`", + py::arg("type"), py::arg("include_conditional") = false) .def( "n_1qb_gates", [](const Circuit &circ) { return circ.count_n_qubit_gates(1); }, diff --git a/pytket/binders/circuit/clexpr.cpp b/pytket/binders/circuit/clexpr.cpp index fa2f73c227..a0589cb393 100644 --- a/pytket/binders/circuit/clexpr.cpp +++ b/pytket/binders/circuit/clexpr.cpp @@ -34,7 +34,7 @@ namespace tket { static std::string qasm_bit_repr( const ClExprTerm &term, const std::map &input_bits) { - if (const int *n = std::get_if(&term)) { + if (const uint64_t *n = std::get_if(&term)) { switch (*n) { case 0: return "0"; @@ -56,7 +56,7 @@ static std::string qasm_bit_repr( static std::string qasm_reg_repr( const ClExprTerm &term, const std::map &input_regs) { - if (const int *n = std::get_if(&term)) { + if (const uint64_t *n = std::get_if(&term)) { std::stringstream ss; ss << *n; return ss.str(); diff --git a/pytket/binders/circuit/main.cpp b/pytket/binders/circuit/main.cpp index ba4d2853f3..cf32612f4d 100644 --- a/pytket/binders/circuit/main.cpp +++ b/pytket/binders/circuit/main.cpp @@ -525,7 +525,9 @@ PYBIND11_MODULE(circuit, m) { "A classical operation applied to multiple bits simultaneously") .value( "ClassicalExpBox", OpType::ClassicalExpBox, - "A box for holding compound classical operations on Bits.") + "A box for holding compound classical operations on Bits.\n" + "DEPRECATED: Please use :py:class:`WiredClExpr` instead. This class " + "will be removed after pytket 1.40.") .value( "MultiplexorBox", OpType::MultiplexorBox, "A multiplexor (i.e. uniformly controlled operations)") diff --git a/pytket/binders/passes.cpp b/pytket/binders/passes.cpp index d47181072f..ef398f8a0d 100644 --- a/pytket/binders/passes.cpp +++ b/pytket/binders/passes.cpp @@ -111,7 +111,7 @@ static PassPtr gen_default_aas_routing_pass( const PassPtr &DecomposeClassicalExp() { // a special box decomposer for Circuits containing - // ClassicalExpBox + // ClassicalExpBox and ClExprOp static const PassPtr pp([]() { Transform t = Transform([](Circuit &circ) { py::module decomposer = @@ -265,23 +265,35 @@ PYBIND11_MODULE(passes, m) { .def( "to_dict", [](const BasePass &base_pass) { - return py::object(base_pass.get_config()).cast(); + return py::cast(serialise(base_pass)); }, ":return: A JSON serializable dictionary representation of the Pass.") .def_static( "from_dict", - [](const py::dict &base_pass_dict) { - return json(base_pass_dict).get(); + [](const py::dict &base_pass_dict, + + std::map> + &custom_deserialisation) { + return deserialise(base_pass_dict, custom_deserialisation); }, "Construct a new Pass instance from a JSON serializable dictionary " - "representation.") + "representation. `custom_deserialisation` is a map between " + "`CustomPass` " + "label attributes and a Circuit to Circuit function matching the " + "`CustomPass` `transform` argument. This allows the construction of " + "some `CustomPass` from JSON. `CustomPass` without a matching entry " + "in " + "`custom_deserialisation` will be rejected.", + py::arg("base_pass_dict"), + py::arg("custom_deserialisation") = + std::map>{}) .def(py::pickle( [](py::object self) { // __getstate__ return py::make_tuple(self.attr("to_dict")()); }, [](const py::tuple &t) { // __setstate__ const json j = t[0].cast(); - return j.get(); + return deserialise(j); })); py::class_, BasePass>( m, "SequencePass", "A sequence of compilation passes.") @@ -296,9 +308,18 @@ PYBIND11_MODULE(passes, m) { "\n:return: a pass that applies the sequence", py::arg("pass_list"), py::arg("strict") = true) .def("__str__", [](const BasePass &) { return ""; }) + .def( + "to_dict", + [](const SequencePass &seq_pass) { + return py::cast( + serialise(std::make_shared(seq_pass))); + }, + ":return: A JSON serializable dictionary representation of the " + "SequencePass.") .def( "get_sequence", &SequencePass::get_sequence, ":return: The underlying sequence of passes."); + py::class_, BasePass>( m, "RepeatPass", "Repeat a pass until its `apply()` method returns False, or if " @@ -462,13 +483,22 @@ PYBIND11_MODULE(passes, m) { py::arg("excluded_opgroups") = std::unordered_set()); m.def( "DecomposeClassicalExp", &DecomposeClassicalExp, - "Replaces each :py:class:`ClassicalExpBox` by a sequence of " - "classical gates."); + "Replaces each :py:class:`ClassicalExpBox` and `ClExprOp` by a sequence " + "of classical gates."); m.def( "DecomposeMultiQubitsCX", &DecomposeMultiQubitsCX, "Converts all multi-qubit gates into CX and single-qubit gates."); m.def( - "GlobalisePhasedX", &GlobalisePhasedX, + "GlobalisePhasedX", + [](bool squash) { + PyErr_WarnEx( + PyExc_DeprecationWarning, + "The GlobalisePhasedX pass is unreliable and deprecated. It will " + "be removed no earlier that three months after the pytket 1.35 " + "release.", + 1); + return GlobalisePhasedX(squash); + }, "Turns all PhasedX and NPhasedX gates into global gates\n\n" "Replaces any PhasedX gates with global NPhasedX gates. " "By default, this transform will squash all single-qubit gates " @@ -478,6 +508,8 @@ PYBIND11_MODULE(passes, m) { "performance. If squashing is disabled, each non-global PhasedX gate " "will be replaced with two global NPhasedX, but any other gates will " "be left untouched." + "\n\nDEPRECATED: This pass will be removed no earlier than three months " + "after the pytket 1.35 release." "\n\n:param squash: Whether to squash the circuit in pre-processing " "(default: true)." "\n\nIf squash=true (default), the `GlobalisePhasedX` transform's " @@ -508,7 +540,7 @@ PYBIND11_MODULE(passes, m) { m.def( "RebaseTket", &RebaseTket, "Converts all gates to CX, TK1 and Phase. " - "(Any Measure, Reset and Collapse operations are left untouched; " + "(Any Measure and Reset operations are left untouched; " "Conditional gates are also allowed.)"); m.def( "RemoveRedundancies", &RemoveRedundancies, @@ -618,7 +650,7 @@ PYBIND11_MODULE(passes, m) { ":math:`(a,b,c)` to generate replacement circuits." "\n\n" ":param gateset: the allowed operations in the rebased circuit " - "(in addition, Measure, Reset and Collapse operations are always allowed " + "(in addition, Measure and Reset operations are always allowed " "and are left alone; conditional operations may be present; and Phase " "gates may also be introduced by the rebase)" "\n:param cx_replacement: the equivalent circuit to replace a CX gate " @@ -628,8 +660,7 @@ PYBIND11_MODULE(passes, m) { "Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the desired " "basis" "\n:return: a pass that rebases to the given gate set (possibly " - "including conditional and phase operations, and Measure, Reset and " - "Collapse)", + "including conditional and phase operations, and Measure and Reset", py::arg("gateset"), py::arg("cx_replacement"), py::arg("tk1_replacement")); @@ -647,7 +678,7 @@ PYBIND11_MODULE(passes, m) { "to each TK1(a,b,c)." "\n\n" ":param gateset: the allowed operations in the rebased circuit " - "(in addition, Measure, Reset and Collapse operations are always allowed " + "(in addition, Measure and Reset always allowed " "and are left alone; conditional operations may be present; and Phase " "gates may also be introduced by the rebase)\n" ":param tk2_replacement: a function which, given the parameters (a,b,c) " @@ -657,7 +688,7 @@ PYBIND11_MODULE(passes, m) { "of an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the " "desired basis\n" ":return: a pass that rebases to the given gate set (possibly including " - "conditional and phase operations, and Measure, Reset and Collapse)", + "conditional and phase operations, and Measure and Reset)", py::arg("gateset"), py::arg("tk2_replacement"), py::arg("tk1_replacement")); m.def( @@ -669,7 +700,7 @@ PYBIND11_MODULE(passes, m) { "Raises an error if no known decompositions can be found, in which case " "try using :py:class:`RebaseCustom` with your own decompositions.\n\n" ":param gateset: Set of supported OpTypes, target gate set. " - "(in addition, Measure, Reset and Collapse operations are always allowed " + "(in addition, Measure and Reset operations are always allowed " "and are left alone; conditional operations may be present; and Phase " "gates may also be introduced by the rebase)\n" ":param allow_swaps: Whether to allow implicit wire swaps. Default to " @@ -731,9 +762,12 @@ PYBIND11_MODULE(passes, m) { "FlattenRelabelRegistersPass", &gen_flatten_relabel_registers_pass, "Removes empty Quantum wires from the Circuit and relabels all Qubit to " "a register from passed name. \n\n:param label: Name to relabel " - "remaining Qubit to, default 'q'.\n:return: A pass that removes empty " + "remaining Qubit to, default 'q'.\n:param relabel_classical_expressions: " + "Whether to relabel arguments of expressions held in `ClassicalExpBox`. " + "\n:return: A pass that removes empty " "wires and relabels.", - py::arg("label") = q_default_reg()); + py::arg("label") = q_default_reg(), + py::arg("relabel_classical_expressions") = true); m.def( "RenameQubitsPass", &gen_rename_qubits_pass, @@ -936,10 +970,19 @@ PYBIND11_MODULE(passes, m) { "\n:param allow_zzphase: If set to True, allows the algorithm to " "implement 2-qubit rotations using ZZPhase gates when deemed " "optimal. Defaults to False." + "\n:param thread_timeout: Sets maximum out of time spent finding a " + "single solution in one thread." + "\n:param only_reduce: Only returns modified circuit if it has " + "fewer two-qubit gates." + "\n:param trials: Sets maximum number of found solutions. The " + "smallest circuit is returned, prioritising the number of 2qb-gates, " + "then the number of gates, then the depth." "\n:return: a pass to perform the simplification", py::arg("discount_rate") = 0.7, py::arg("depth_weight") = 0.3, py::arg("max_lookahead") = 500, py::arg("max_tqe_candidates") = 500, - py::arg("seed") = 0, py::arg("allow_zzphase") = false); + py::arg("seed") = 0, py::arg("allow_zzphase") = false, + py::arg("thread_timeout") = 100, py::arg("only_reduce") = false, + py::arg("trials") = 1); m.def( "PauliSquash", &PauliSquash, "Applies :py:meth:`PauliSimp` followed by " @@ -1012,7 +1055,7 @@ PYBIND11_MODULE(passes, m) { m.def( "CnXPairwiseDecomposition", &CnXPairwiseDecomposition, - "Decompose CnX gates to 2-qubit gates and single qubit gates. " + "Decompose CnX gates to 2-qubit gates `fand single qubit gates. " "For every two CnX gates, reorder their control qubits to improve " "the chance of gate cancellation"); diff --git a/pytket/binders/transform.cpp b/pytket/binders/transform.cpp index 21724759c3..286fc6062f 100644 --- a/pytket/binders/transform.cpp +++ b/pytket/binders/transform.cpp @@ -386,7 +386,16 @@ PYBIND11_MODULE(transform, m) { "DecomposeNPhasedX", &Transforms::decompose_NPhasedX, "Decompose NPhasedX gates into single-qubit PhasedX gates.") .def_static( - "GlobalisePhasedX", &Transforms::globalise_PhasedX, + "GlobalisePhasedX", + [](bool squash) { + PyErr_WarnEx( + PyExc_DeprecationWarning, + "The GlobalisePhasedX transform is unreliable and deprecated. " + "It will be removed no earlier than three months after the " + "pytket 1.35 release.", + 1); + return Transforms::globalise_PhasedX(squash); + }, "Turns all PhasedX and NPhasedX gates into global gates\n\n" "Replaces any PhasedX gates with global NPhasedX gates. " "By default, this transform will squash all single-qubit gates " @@ -396,6 +405,8 @@ PYBIND11_MODULE(transform, m) { "performance. If squashing is disabled, each non-global PhasedX gate " "will be replaced with two global NPhasedX, but any other gates will " "be left untouched." + "\n\nDEPRECATED: This transform will be removed no earlier than " + "three months after the pytket 1.35 release." "\n\n:param squash: Whether to squash the circuit in pre-processing " "(default: true)." "\n\nIf squash=true (default), the `GlobalisePhasedX` transform's " @@ -440,10 +451,16 @@ PYBIND11_MODULE(transform, m) { "\n:param allow_zzphase: If set to True, allows the algorithm to " "implement 2-qubit rotations using ZZPhase gates when deemed " "optimal. Defaults to False." + "\n:param thread_timeout: Sets maximum out of time spent finding a " + "single solution in one thread." + "\n:param trials: Sets maximum number of found solutions. The " + "smallest circuit is returned, prioritising the number of 2qb-gates, " + "then the number of gates, then the depth." "\n:return: a pass to perform the simplification", py::arg("discount_rate") = 0.7, py::arg("depth_weight") = 0.3, py::arg("max_tqe_candidates") = 500, py::arg("max_lookahead") = 500, - py::arg("seed") = 0, py::arg("allow_zzphase") = false) + py::arg("seed") = 0, py::arg("allow_zzphase") = false, + py::arg("thread_timeout") = 100, py::arg("trials") = 1) .def_static( "ZZPhaseToRz", &Transforms::ZZPhase_to_Rz, "Fixes all ZZPhase gate angles to [-1, 1) half turns.") diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 80e7e0b31c..123ac8339a 100644 --- a/pytket/conanfile.py +++ b/pytket/conanfile.py @@ -1,5 +1,5 @@ from conan import ConanFile -from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps +from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout class pytketRecipe(ConanFile): @@ -36,9 +36,9 @@ def requirements(self): self.requires("nlohmann_json/3.11.3") self.requires("pybind11/2.13.6") self.requires("pybind11_json/0.2.14") - self.requires("symengine/0.12.0") + self.requires("symengine/0.13.0") self.requires("tkassert/0.3.4@tket/stable") - self.requires("tket/1.3.35@tket/stable") + self.requires("tket/1.3.48@tket/stable") self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") self.requires("tktokenswap/0.3.9@tket/stable") diff --git a/pytket/docs/README.md b/pytket/docs/README.md index c4e8ae83db..4924fd60af 100644 --- a/pytket/docs/README.md +++ b/pytket/docs/README.md @@ -3,12 +3,10 @@ Pytket Docs # Clone repository ``` -git@github.com:CQCL/tket.git +git clone git@github.com:CQCL/tket.git --recurse-submodules ``` -``` -git submodule update --recursive --init -``` +# Move to docs directory ``` cd pytket/docs @@ -19,13 +17,23 @@ cd pytket/docs ``` poetry install ``` + +# Install pytket + +The pytket package is not installed as a poetry dependency so needs to be installed seperately + +``` +poetry run pip install -U pytket +``` +You can install a pypi version as above or an editable wheel. + # Build html ``` -poetry run make html +poetry run bash ./build-docs.sh ``` -# Serve build +# Serve built html locally ``` -npx serve ./build/html +npx serve build ``` diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 143d5592c8..bd7a814086 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -1,6 +1,44 @@ Changelog ========= +1.35.0 (November 2024) +---------------------- + +Features: + +* Add `clexpr.check_register_alignments()` method to check register alignments + in `ClExprOp`. +* Use `ClExprOp` instead of `ClassicalExpBox` when deconstructing complex + conditions. +* Add `custom_deserialisation` argument to `BasePass` and `SequencePass` + `from_dict` method to support construction of `CustomPass` from json. +* Add `thread_timeout`, `only_reduce`, and `trials` arguments + to `GreedyPauliSimp`. +* Add option to not relabel `ClassicalExpBox` when calling `rename_units` + and `flatten_registers` +* Implement `dagger()` and `transpose()` for `CustomGate`. +* Use `ClExprOp` by default when converting from QASM. +* Extend `DecomposeClassicalExp` to handle `ClExprOp` as well as + `ClassicalExpBox`. +* Add convenience method `Circuit.add_clecpr_from_logicexp()`. +* Remove `OpType::Collapse` from the `GateSetPredicate` of `gen_auto_rebase_pass`. + +Deprecations: + +* Deprecate `ClassicalExpBox` and related methods, in favour of `ClExprOp`. +* Deprecate `GlobalisePhasedX` pass and transform. + +Fixes: + +* Fix `symbol_substitution` not preserving opgroups. +* Remove hardware inefficient circuit construction in `_tk1_to_rzsx` +* Support converting conditional `RangePredicate`s to QASM. +* Fix `maxwidth` parameter of `circuit_from_qasm_str` +* Add `scratch_reg_resize_pass` to `circuit_from_qasm_str` +* Reject incompete classical registers in pytket to qasm conversion +* Add parameter `include_conditional` to `n_gates_of_type` to include + conditional gates in the count + 1.34.0 (October 2024) --------------------- diff --git a/pytket/docs/check_circuit_class_docs.py b/pytket/docs/check_circuit_class_docs.py index 816bc4c35d..90b728bc54 100644 --- a/pytket/docs/check_circuit_class_docs.py +++ b/pytket/docs/check_circuit_class_docs.py @@ -6,7 +6,7 @@ # This script is used to check that no methods or properties of # the Circuit class are left out in the circuit_class.rst file. -with open("circuit_class.rst", "r", encoding="utf-8") as file: +with open("circuit_class.rst", encoding="utf-8") as file: rst = file.read() in_methods = {p[0] for p in inspect.getmembers(Circuit)} diff --git a/pytket/pytket/__init__.py b/pytket/pytket/__init__.py old mode 100755 new mode 100644 index 8b4b5fee3a..341ed1fd2f --- a/pytket/pytket/__init__.py +++ b/pytket/pytket/__init__.py @@ -14,20 +14,20 @@ """Python Interface to tket """ +import pytket.architecture # noqa: I001 from pytket.circuit import ( Circuit, OpType, ) -from pytket.unit_id import ( - Qubit, - Bit, -) import pytket.mapping -import pytket.architecture import pytket.placement import pytket.transform -from pytket.config import PytketConfig, get_config_file_path from pytket._version import __version__ +from pytket.config import PytketConfig, get_config_file_path +from pytket.unit_id import ( + Bit, + Qubit, +) # Create pytket config file if it does not exist: pytket_config_file = get_config_file_path() diff --git a/pytket/pytket/_tket/circuit.pyi b/pytket/pytket/_tket/circuit.pyi index 4cbc158def..6105ee6703 100644 --- a/pytket/pytket/_tket/circuit.pyi +++ b/pytket/pytket/_tket/circuit.pyi @@ -1438,6 +1438,7 @@ class Circuit: def add_classicalexpbox_bit(self, expression: pytket.circuit.logic_exp.BitLogicExp, target: typing.Sequence[pytket._tket.unit_id.Bit], **kwargs: Any) -> Circuit: """ Append a :py:class:`ClassicalExpBox` over Bit to the circuit. + DEPRECATED: Please use :py:meth:`add_clexpr` instead. This method will be removed after pytket 1.40. :param classicalexpbox: The box to append :param args: Indices of the qubits to append the box to @@ -1446,6 +1447,7 @@ class Circuit: def add_classicalexpbox_register(self, expression: pytket.circuit.logic_exp.RegLogicExp, target: typing.Sequence[pytket._tket.unit_id.Bit], **kwargs: Any) -> Circuit: """ Append a :py:class:`ClassicalExpBox` over BitRegister to the circuit. + DEPRECATED: Please use :py:meth:`add_clexpr` instead. This method will be removed after pytket 1.40. :param classicalexpbox: The box to append :param args: Indices of the qubits to append the box to @@ -1459,6 +1461,22 @@ class Circuit: :param args: The bits to apply the expression to :return: the new :py:class:`Circuit` """ + def add_clexpr_from_logicexp(self, exp: pytket.circuit.logic_exp.LogicExp, output_bits: typing.Sequence[pytket._tket.unit_id.Bit], **kwargs: Any) -> Circuit: + """ + Append a :py:class:`ClExprOp` defined in terms of a logical expression. + + Example: + >>> c = Circuit() + >>> x_reg = c.add_c_register('x', 3) + >>> y_reg = c.add_c_register('y', 3) + >>> z_reg = c.add_c_register('z', 3) + >>> c.add_clexpr_from_logicexp(x_reg | y_reg, z_reg.to_list()) + >>> [ClExpr x[0], x[1], x[2], y[0], y[1], y[2], z[0], z[1], z[2]; ] + + :param exp: logical expression + :param output_bits: list of bits in output + :return: the updated circuit + """ @typing.overload def add_conditional_barrier(self, barrier_qubits: typing.Sequence[int], barrier_bits: typing.Sequence[int], condition_bits: typing.Sequence[int], value: int, data: str = '') -> Circuit: """ @@ -2104,9 +2122,11 @@ class Circuit: :param types: the set of operation types of interest :return: the circuit depth with respect to operations matching an element of `types` """ - def flatten_registers(self) -> dict[pytket._tket.unit_id.UnitID, pytket._tket.unit_id.UnitID]: + def flatten_registers(self, relabel_classical_expression: bool = True) -> dict[pytket._tket.unit_id.UnitID, pytket._tket.unit_id.UnitID]: """ Combines all qubits into a single register namespace with the default name, and likewise for bits + + :param relabel_classical_expression: Determines whether python classical expressions held in `ClassicalExpBox` have their expression relabelled to match relabelled Bit. """ def free_symbols(self) -> set[sympy.Symbol]: """ @@ -2235,7 +2255,7 @@ class Circuit: """ Returns the number of vertices in the dag with two quantum edges.Ignores Input, Create, Output, Discard, Reset, Measure and Barrier vertices. """ - def n_gates_of_type(self, type: OpType) -> int: + def n_gates_of_type(self, type: OpType, include_conditional: bool = False) -> int: """ Returns the number of vertices in the dag of a given operation type. @@ -2246,6 +2266,9 @@ class Circuit: 2 :param type: The operation type to search for + + :param include_conditional: if set to true, conditional gates will be counted, too + :return: the number of operations matching `type` """ def n_nqb_gates(self, size: int) -> int: @@ -3614,6 +3637,7 @@ class OpType: MultiBit : A classical operation applied to multiple bits simultaneously ClassicalExpBox : A box for holding compound classical operations on Bits. + DEPRECATED: Please use :py:class:`WiredClExpr` instead. This class will be removed after pytket 1.40. MultiplexorBox : A multiplexor (i.e. uniformly controlled operations) diff --git a/pytket/pytket/_tket/passes.pyi b/pytket/pytket/_tket/passes.pyi index efc45514eb..9a59fae388 100644 --- a/pytket/pytket/_tket/passes.pyi +++ b/pytket/pytket/_tket/passes.pyi @@ -18,9 +18,9 @@ class BasePass: def _pybind11_conduit_v1_(*args, **kwargs): # type: ignore ... @staticmethod - def from_dict(arg0: dict) -> BasePass: + def from_dict(base_pass_dict: dict, custom_deserialisation: dict[str, typing.Callable[[pytket._tket.circuit.Circuit], pytket._tket.circuit.Circuit]] = {}) -> BasePass: """ - Construct a new Pass instance from a JSON serializable dictionary representation. + Construct a new Pass instance from a JSON serializable dictionary representation. `custom_deserialisation` is a map between `CustomPass` label attributes and a Circuit to Circuit function matching the `CustomPass` `transform` argument. This allows the construction of some `CustomPass` from JSON. `CustomPass` without a matching entry in `custom_deserialisation` will be rejected. """ def __getstate__(self) -> tuple: ... @@ -54,7 +54,7 @@ class BasePass: :param after_apply: Invoked after a pass is applied. The CompilationUnit and a summary of the pass configuration are passed into the callback. :return: True if pass modified the circuit, else False """ - def to_dict(self) -> dict: + def to_dict(self) -> typing.Any: """ :return: A JSON serializable dictionary representation of the Pass. """ @@ -227,6 +227,10 @@ class SequencePass(BasePass): """ :return: The underlying sequence of passes. """ + def to_dict(self) -> typing.Any: + """ + :return: A JSON serializable dictionary representation of the SequencePass. + """ def AASRouting(arc: pytket._tket.architecture.Architecture, **kwargs: Any) -> BasePass: """ Construct a pass to relabel :py:class:`Circuit` Qubits to :py:class:`Device` Nodes, and then use architecture-aware synthesis to route the circuit. In the steps of the pass the circuit will be converted to CX, Rz, H gateset. The limited connectivity of the :py:class:`Architecture` is used for the routing. The direction of the edges is ignored. The placement used is GraphPlacement. This pass can take a few parameters for the routing, described below: @@ -245,7 +249,7 @@ def AutoRebase(gateset: set[pytket._tket.circuit.OpType], allow_swaps: bool = Fa Attempt to generate a rebase pass automatically for the given target gateset. Checks if there are known existing decompositions to target gateset and TK1 to target gateset and uses those to construct a custom rebase. Raises an error if no known decompositions can be found, in which case try using :py:class:`RebaseCustom` with your own decompositions. - :param gateset: Set of supported OpTypes, target gate set. (in addition, Measure, Reset and Collapse operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) + :param gateset: Set of supported OpTypes, target gate set. (in addition, Measure and Reset operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) :param allow_swaps: Whether to allow implicit wire swaps. Default to False. """ def AutoSquash(singleqs: set[pytket._tket.circuit.OpType]) -> BasePass: @@ -286,7 +290,7 @@ def CliffordSimp(allow_swaps: bool = True) -> BasePass: """ def CnXPairwiseDecomposition() -> BasePass: """ - Decompose CnX gates to 2-qubit gates and single qubit gates. For every two CnX gates, reorder their control qubits to improve the chance of gate cancellation + Decompose CnX gates to 2-qubit gates `fand single qubit gates. For every two CnX gates, reorder their control qubits to improve the chance of gate cancellation """ def CommuteThroughMultis() -> BasePass: """ @@ -338,7 +342,7 @@ def DecomposeBoxes(excluded_types: set[pytket._tket.circuit.OpType] = set(), exc """ def DecomposeClassicalExp() -> BasePass: """ - Replaces each :py:class:`ClassicalExpBox` by a sequence of classical gates. + Replaces each :py:class:`ClassicalExpBox` and `ClExprOp` by a sequence of classical gates. """ def DecomposeMultiQubitsCX() -> BasePass: """ @@ -406,11 +410,12 @@ def FlattenRegisters() -> BasePass: """ Merges all quantum and classical registers into their respective default registers with contiguous indexing. """ -def FlattenRelabelRegistersPass(label: str = 'q') -> BasePass: +def FlattenRelabelRegistersPass(label: str = 'q', relabel_classical_expressions: bool = True) -> BasePass: """ Removes empty Quantum wires from the Circuit and relabels all Qubit to a register from passed name. :param label: Name to relabel remaining Qubit to, default 'q'. + :param relabel_classical_expressions: Whether to relabel arguments of expressions held in `ClassicalExpBox`. :return: A pass that removes empty wires and relabels. """ def FullMappingPass(arc: pytket._tket.architecture.Architecture, placer: pytket._tket.placement.Placement, config: typing.Sequence[pytket._tket.mapping.RoutingMethod]) -> BasePass: @@ -434,13 +439,15 @@ def GlobalisePhasedX(squash: bool = True) -> BasePass: Replaces any PhasedX gates with global NPhasedX gates. By default, this transform will squash all single-qubit gates to PhasedX and Rz gates before proceeding further. Existing non-global NPhasedX will not be preserved. This is the recommended setting for best performance. If squashing is disabled, each non-global PhasedX gate will be replaced with two global NPhasedX, but any other gates will be left untouched. + DEPRECATED: This pass will be removed no earlier than three months after the pytket 1.35 release. + :param squash: Whether to squash the circuit in pre-processing (default: true). If squash=true (default), the `GlobalisePhasedX` transform's `apply` method will always return true. For squash=false, `apply()` will return true if the circuit was changed and false otherwise. It is not recommended to use this pass with symbolic expressions, as in certain cases a blow-up in symbolic expression sizes may occur. """ -def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3, max_lookahead: int = 500, max_tqe_candidates: int = 500, seed: int = 0, allow_zzphase: bool = False) -> BasePass: +def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3, max_lookahead: int = 500, max_tqe_candidates: int = 500, seed: int = 0, allow_zzphase: bool = False, thread_timeout: int = 100, only_reduce: bool = False, trials: int = 1) -> BasePass: """ Construct a pass that converts a circuit into a graph of Pauli gadgets to account for commutation and phase folding, and resynthesises them using a greedy algorithm adapted from arxiv.org/abs/2103.08602. The method for synthesising the final Clifford operator is adapted from arxiv.org/abs/2305.10966. @@ -450,6 +457,9 @@ def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3, max_l :param max_lookahead: Maximum lookahead when evaluating each Clifford gate candidate. Default to 500. :param seed: Unsigned integer seed used for sampling candidates and tie breaking. Default to 0. :param allow_zzphase: If set to True, allows the algorithm to implement 2-qubit rotations using ZZPhase gates when deemed optimal. Defaults to False. + :param thread_timeout: Sets maximum out of time spent finding a single solution in one thread. + :param only_reduce: Only returns modified circuit if it has fewer two-qubit gates. + :param trials: Sets maximum number of found solutions. The smallest circuit is returned, prioritising the number of 2qb-gates, then the number of gates, then the depth. :return: a pass to perform the simplification """ def GuidedPauliSimp(strat: pytket._tket.transform.PauliSynthStrat = pytket._tket.transform.PauliSynthStrat.Sets, cx_config: pytket._tket.circuit.CXConfigType = pytket._tket.circuit.CXConfigType.Snake) -> BasePass: @@ -555,10 +565,10 @@ def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], cx_replacement: pytk 3. converts any single-qubit gates not in the gate type set to the form :math:`\\mathrm{Rz}(a)\\mathrm{Rx}(b)\\mathrm{Rz}(c)` (in matrix-multiplication order, i.e. reverse order in the circuit); 4. applies the `tk1_replacement` function to each of these triples :math:`(a,b,c)` to generate replacement circuits. - :param gateset: the allowed operations in the rebased circuit (in addition, Measure, Reset and Collapse operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) + :param gateset: the allowed operations in the rebased circuit (in addition, Measure and Reset operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) :param cx_replacement: the equivalent circuit to replace a CX gate using two qubit gates from the desired basis (can use any single qubit OpTypes) :param tk1_replacement: a function which, given the parameters of an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the desired basis - :return: a pass that rebases to the given gate set (possibly including conditional and phase operations, and Measure, Reset and Collapse) + :return: a pass that rebases to the given gate set (possibly including conditional and phase operations, and Measure and Reset """ @typing.overload def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], tk2_replacement: typing.Callable[[sympy.Expr | float, sympy.Expr | float, sympy.Expr | float], pytket._tket.circuit.Circuit], tk1_replacement: typing.Callable[[sympy.Expr | float, sympy.Expr | float, sympy.Expr | float], pytket._tket.circuit.Circuit]) -> BasePass: @@ -570,14 +580,14 @@ def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], tk2_replacement: typ 3. converts any single-qubit gates not in the gate type set to TK1; 4. if TK2 is not in `gateset`. applies the `tk1_replacement` function to each TK1(a,b,c). - :param gateset: the allowed operations in the rebased circuit (in addition, Measure, Reset and Collapse operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) + :param gateset: the allowed operations in the rebased circuit (in addition, Measure and Reset always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) :param tk2_replacement: a function which, given the parameters (a,b,c) of an XXPhase(a)YYPhase(b)ZZPhase(c) triple, returns an equivalent circuit in the desired basis :param tk1_replacement: a function which, given the parameters (a,b,c) of an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the desired basis - :return: a pass that rebases to the given gate set (possibly including conditional and phase operations, and Measure, Reset and Collapse) + :return: a pass that rebases to the given gate set (possibly including conditional and phase operations, and Measure and Reset) """ def RebaseTket() -> BasePass: """ - Converts all gates to CX, TK1 and Phase. (Any Measure, Reset and Collapse operations are left untouched; Conditional gates are also allowed.) + Converts all gates to CX, TK1 and Phase. (Any Measure and Reset operations are left untouched; Conditional gates are also allowed.) """ def RemoveBarriers() -> BasePass: """ diff --git a/pytket/pytket/_tket/transform.pyi b/pytket/pytket/_tket/transform.pyi index 4801b4238c..4b137ff45f 100644 --- a/pytket/pytket/_tket/transform.pyi +++ b/pytket/pytket/_tket/transform.pyi @@ -158,6 +158,8 @@ class Transform: Replaces any PhasedX gates with global NPhasedX gates. By default, this transform will squash all single-qubit gates to PhasedX and Rz gates before proceeding further. Existing non-global NPhasedX will not be preserved. This is the recommended setting for best performance. If squashing is disabled, each non-global PhasedX gate will be replaced with two global NPhasedX, but any other gates will be left untouched. + DEPRECATED: This transform will be removed no earlier than three months after the pytket 1.35 release. + :param squash: Whether to squash the circuit in pre-processing (default: true). If squash=true (default), the `GlobalisePhasedX` transform's `apply` method will always return true. For squash=false, `apply()` will return true if the circuit was changed and false otherwise. @@ -165,7 +167,7 @@ class Transform: It is not recommended to use this transformation with symbolic expressions, as in certain cases a blow-up in symbolic expression sizes may occur. """ @staticmethod - def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3, max_tqe_candidates: int = 500, max_lookahead: int = 500, seed: int = 0, allow_zzphase: bool = False) -> Transform: + def GreedyPauliSimp(discount_rate: float = 0.7, depth_weight: float = 0.3, max_tqe_candidates: int = 500, max_lookahead: int = 500, seed: int = 0, allow_zzphase: bool = False, thread_timeout: int = 100, trials: int = 1) -> Transform: """ Convert a circuit into a graph of Pauli gadgets to account for commutation and phase folding, and resynthesises them using a greedy algorithm adapted from arxiv.org/abs/2103.08602. The method for synthesising the final Clifford operator is adapted from arxiv.org/abs/2305.10966. @@ -175,6 +177,8 @@ class Transform: :param max_lookahead: Maximum lookahead when evaluating each Clifford gate candidate. Default to 500. :param seed: Unsigned integer seed used for sampling candidates and tie breaking. Default to 0. :param allow_zzphase: If set to True, allows the algorithm to implement 2-qubit rotations using ZZPhase gates when deemed optimal. Defaults to False. + :param thread_timeout: Sets maximum out of time spent finding a single solution in one thread. + :param trials: Sets maximum number of found solutions. The smallest circuit is returned, prioritising the number of 2qb-gates, then the number of gates, then the depth. :return: a pass to perform the simplification """ @staticmethod diff --git a/pytket/pytket/backends/__init__.py b/pytket/pytket/backends/__init__.py index 1f838b2eab..f124c3f914 100644 --- a/pytket/pytket/backends/__init__.py +++ b/pytket/pytket/backends/__init__.py @@ -17,6 +17,6 @@ __path__ = __import__("pkgutil").extend_path(__path__, __name__) from .backend import Backend +from .backend_exceptions import CircuitNotRunError, CircuitNotValidError from .resulthandle import ResultHandle from .status import CircuitStatus, StatusEnum -from .backend_exceptions import CircuitNotRunError, CircuitNotValidError diff --git a/pytket/pytket/backends/backend.py b/pytket/pytket/backends/backend.py index 39ac667f1b..92d703972e 100644 --- a/pytket/pytket/backends/backend.py +++ b/pytket/pytket/backends/backend.py @@ -15,40 +15,29 @@ """ Abstract base class for all Backend encapsulations.""" import warnings from abc import ABC, abstractmethod -from typing import ( - Dict, - Iterable, - List, - Optional, - Sequence, - Union, - Any, - cast, - overload, -) +from collections.abc import Iterable, Sequence from importlib import import_module from types import ModuleType - -from typing_extensions import Literal +from typing import Any, Literal, cast, overload from pytket.circuit import Bit, Circuit, OpType from pytket.passes import BasePass from pytket.pauli import QubitPauliString from pytket.predicates import Predicate +from pytket.utils import QubitPauliOperator from pytket.utils.outcomearray import OutcomeArray from pytket.utils.results import KwargTypes -from pytket.utils import QubitPauliOperator from .backend_exceptions import ( - CircuitNotValidError, CircuitNotRunError, + CircuitNotValidError, ) from .backendinfo import BackendInfo from .backendresult import BackendResult from .resulthandle import ResultHandle, _ResultIdTuple from .status import CircuitStatus -ResultCache = Dict[str, Any] +ResultCache = dict[str, Any] class ResultHandleTypeError(Exception): @@ -73,7 +62,7 @@ class Backend(ABC): _persistent_handles = False def __init__(self) -> None: - self._cache: Dict[ResultHandle, ResultCache] = {} + self._cache: dict[ResultHandle, ResultCache] = {} @staticmethod def empty_result(circuit: Circuit, n_shots: int) -> BackendResult: @@ -85,7 +74,7 @@ def empty_result(circuit: Circuit, n_shots: int) -> BackendResult: @property @abstractmethod - def required_predicates(self) -> List[Predicate]: + def required_predicates(self) -> list[Predicate]: """ The minimum set of predicates that a circuit must satisfy before it can be successfully run on this backend. @@ -107,7 +96,7 @@ def valid_circuit(self, circuit: Circuit) -> bool: return all(pred.verify(circuit) for pred in self.required_predicates) def _check_all_circuits( - self, circuits: Iterable[Circuit], nomeasure_warn: Optional[bool] = None + self, circuits: Iterable[Circuit], nomeasure_warn: bool | None = None ) -> bool: if nomeasure_warn is None: nomeasure_warn = not ( @@ -185,7 +174,7 @@ def get_compiled_circuit( def get_compiled_circuits( self, circuits: Sequence[Circuit], optimisation_level: int = 2 - ) -> List[Circuit]: + ) -> list[Circuit]: """Compile a sequence of circuits with :py:meth:`default_compilation_pass` and return the list of compiled circuits (does not act in place). @@ -237,15 +226,14 @@ def _check_handle_type(self, reshandle: ResultHandle) -> None: isinstance(idval, ty) for idval, ty in zip(reshandle, self._result_id_type) ): raise ResultHandleTypeError( - "{0!r} does not match expected identifier types {1}".format( - reshandle, self._result_id_type - ) + f"{reshandle!r} does not match expected " + f"identifier types {self._result_id_type}" ) def process_circuit( self, circuit: Circuit, - n_shots: Optional[int] = None, + n_shots: int | None = None, valid_check: bool = True, **kwargs: KwargTypes, ) -> ResultHandle: @@ -262,10 +250,10 @@ def process_circuit( def process_circuits( self, circuits: Sequence[Circuit], - n_shots: Optional[Union[int, Sequence[int]]] = None, + n_shots: int | Sequence[int] | None = None, valid_check: bool = True, **kwargs: KwargTypes, - ) -> List[ResultHandle]: + ) -> list[ResultHandle]: """ Submit circuits to the backend for running. The results will be stored in the backend's result cache to be retrieved by the corresponding @@ -316,7 +304,7 @@ def empty_cache(self) -> None: """Manually empty the result cache on the backend.""" self._cache = {} - def pop_result(self, handle: ResultHandle) -> Optional[ResultCache]: + def pop_result(self, handle: ResultHandle) -> ResultCache | None: """Remove cache entry corresponding to handle from the cache and return. :param handle: ResultHandle object @@ -348,7 +336,7 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul def get_results( self, handles: Iterable[ResultHandle], **kwargs: KwargTypes - ) -> List[BackendResult]: + ) -> list[BackendResult]: """Return results corresponding to handles. :param handles: Iterable of handles @@ -372,7 +360,7 @@ def get_results( def run_circuit( self, circuit: Circuit, - n_shots: Optional[int] = None, + n_shots: int | None = None, valid_check: bool = True, **kwargs: KwargTypes, ) -> BackendResult: @@ -396,10 +384,10 @@ def run_circuit( def run_circuits( self, circuits: Sequence[Circuit], - n_shots: Optional[Union[int, Sequence[int]]] = None, + n_shots: int | Sequence[int] | None = None, valid_check: bool = True, **kwargs: KwargTypes, - ) -> List[BackendResult]: + ) -> list[BackendResult]: """ Submits circuits to the backend and returns results @@ -430,7 +418,7 @@ def cancel(self, handle: ResultHandle) -> None: raise NotImplementedError("Backend does not support job cancellation.") @property - def backend_info(self) -> Optional[BackendInfo]: + def backend_info(self) -> BackendInfo | None: """Retrieve all Backend properties in a BackendInfo object, including device architecture, supported gate set, gate errors and other hardware-specific information. @@ -441,7 +429,7 @@ def backend_info(self) -> Optional[BackendInfo]: raise NotImplementedError("Backend does not provide any device properties.") @classmethod - def available_devices(cls, **kwargs: Any) -> List[BackendInfo]: + def available_devices(cls, **kwargs: Any) -> list[BackendInfo]: """Retrieve all available devices as a list of BackendInfo objects, including device name, architecture, supported gate set, gate errors, and other hardware-specific information. @@ -516,7 +504,7 @@ def supports_contextual_optimisation(self) -> bool: See :py:meth:`process_circuits`.""" return self._supports_contextual_optimisation - def _get_extension_module(self) -> Optional[ModuleType]: + def _get_extension_module(self) -> ModuleType | None: """Return the extension module of the backend if it belongs to a pytket-extension package. @@ -530,7 +518,7 @@ def _get_extension_module(self) -> Optional[ModuleType]: return import_module(".".join(mod_parts)) @property - def __extension_name__(self) -> Optional[str]: + def __extension_name__(self) -> str | None: """Retrieve the extension name of the backend if it belongs to a pytket-extension package. @@ -544,7 +532,7 @@ def __extension_name__(self) -> Optional[str]: return None @property - def __extension_version__(self) -> Optional[str]: + def __extension_version__(self) -> str | None: """Retrieve the extension version of the backend if it belongs to a pytket-extension package. @@ -560,36 +548,36 @@ def __extension_version__(self) -> Optional[str]: @overload @staticmethod def _get_n_shots_as_list( - n_shots: Union[None, int, Sequence[Optional[int]]], + n_shots: None | int | Sequence[int | None], n_circuits: int, optional: Literal[False], - ) -> List[int]: ... + ) -> list[int]: ... @overload @staticmethod def _get_n_shots_as_list( - n_shots: Union[None, int, Sequence[Optional[int]]], + n_shots: None | int | Sequence[int | None], n_circuits: int, optional: Literal[True], set_zero: Literal[True], - ) -> List[int]: ... + ) -> list[int]: ... @overload @staticmethod def _get_n_shots_as_list( - n_shots: Union[None, int, Sequence[Optional[int]]], + n_shots: None | int | Sequence[int | None], n_circuits: int, optional: bool = True, set_zero: bool = False, - ) -> Union[List[Optional[int]], List[int]]: ... + ) -> list[int | None] | list[int]: ... @staticmethod def _get_n_shots_as_list( - n_shots: Union[None, int, Sequence[Optional[int]]], + n_shots: None | int | Sequence[int | None], n_circuits: int, optional: bool = True, set_zero: bool = False, - ) -> Union[List[Optional[int]], List[int]]: + ) -> list[int | None] | list[int]: """ Convert any admissible n_shots value into List[Optional[int]] format. @@ -609,9 +597,9 @@ def _get_n_shots_as_list( :return: a list of length `n_circuits`, the converted argument """ - n_shots_list: List[Optional[int]] = [] + n_shots_list: list[int | None] = [] - def validate_n_shots(n: Optional[int]) -> bool: + def validate_n_shots(n: int | None) -> bool: return optional or (n is not None and n > 0) if set_zero and not optional: diff --git a/pytket/pytket/backends/backend_exceptions.py b/pytket/pytket/backends/backend_exceptions.py index cf872edd1f..ddae83e519 100644 --- a/pytket/pytket/backends/backend_exceptions.py +++ b/pytket/pytket/backends/backend_exceptions.py @@ -12,14 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, Optional from .resulthandle import ResultHandle class CircuitNotValidError(Exception): """Raised when a submitted circuit does not satisfy all predicates""" - def __init__(self, message: Union[str, int], failed_pred: Optional[str] = None): + def __init__(self, message: str | int, failed_pred: str | None = None): if isinstance(message, int): message = ( "Circuit with index {0} in submitted does not satisfy " @@ -34,8 +33,8 @@ class CircuitNotRunError(Exception): def __init__(self, handle: ResultHandle): super().__init__( - "Circuit corresponding to {0!r} ".format(handle) - + "has not been run by this backend instance." + f"Circuit corresponding to {handle!r} " + "has not been run by this backend instance." ) @@ -43,6 +42,4 @@ class InvalidResultType(Exception): """Raised when a BackendResult instance cannot produce the required result type.""" def __init__(self, result_type: str): - super().__init__( - "BackendResult cannot produce result of type {}.".format(result_type) - ) + super().__init__(f"BackendResult cannot produce result of type {result_type}.") diff --git a/pytket/pytket/backends/backendinfo.py b/pytket/pytket/backends/backendinfo.py index 0b857587fa..8e293dad93 100644 --- a/pytket/pytket/backends/backendinfo.py +++ b/pytket/pytket/backends/backendinfo.py @@ -14,19 +14,19 @@ """ BackendInfo class: additional information on Backends """ -from dataclasses import dataclass, field, asdict -from typing import Any, Dict, List, Optional, Set, Tuple, Union +from dataclasses import asdict, dataclass, field +from typing import Any from pytket.architecture import Architecture, FullyConnected from pytket.circuit import Node, OpType -_OpTypeErrs = Dict[OpType, float] -_Edge = Tuple[Node, Node] +_OpTypeErrs = dict[OpType, float] +_Edge = tuple[Node, Node] def _serialize_all_node_gate_errors( - d: Optional[Dict[Node, _OpTypeErrs]] -) -> Optional[List[List]]: + d: dict[Node, _OpTypeErrs] | None +) -> list[list] | None: if d is None: return None return [ @@ -36,8 +36,8 @@ def _serialize_all_node_gate_errors( def _deserialize_all_node_gate_errors( - l: Optional[List[List]], -) -> Optional[Dict[Node, _OpTypeErrs]]: + l: list[list] | None, +) -> dict[Node, _OpTypeErrs] | None: if l is None: return None return { @@ -46,9 +46,7 @@ def _deserialize_all_node_gate_errors( } -def _serialize_all_edge_gate_errors( - d: Optional[Dict[_Edge, _OpTypeErrs]] -) -> Optional[List]: +def _serialize_all_edge_gate_errors(d: dict[_Edge, _OpTypeErrs] | None) -> list | None: if d is None: return None return [ @@ -58,8 +56,8 @@ def _serialize_all_edge_gate_errors( def _deserialize_all_edge_gate_errors( - l: Optional[List], -) -> Optional[Dict[_Edge, _OpTypeErrs]]: + l: list | None, +) -> dict[_Edge, _OpTypeErrs] | None: if l is None: return None return { @@ -71,64 +69,64 @@ def _deserialize_all_edge_gate_errors( def _serialize_all_readout_errors( - d: Optional[Dict[Node, List[List[float]]]] -) -> Optional[List[List]]: + d: dict[Node, list[list[float]]] | None +) -> list[list] | None: if d is None: return None return [[n.to_list(), errs] for n, errs in d.items()] def _deserialize_all_readout_errors( - l: Optional[List[List]], -) -> Optional[Dict[Node, List[List[float]]]]: + l: list[list] | None, +) -> dict[Node, list[list[float]]] | None: if l is None: return None return {Node.from_list(n): errs for n, errs in l} def _serialize_averaged_node_gate_errors( - d: Optional[Dict[Node, float]] -) -> Optional[List[List]]: + d: dict[Node, float] | None +) -> list[list] | None: if d is None: return None return [[n.to_list(), err] for n, err in d.items()] def _deserialize_averaged_node_gate_errors( - l: Optional[List[List]], -) -> Optional[Dict[Node, float]]: + l: list[list] | None, +) -> dict[Node, float] | None: if l is None: return None return {Node.from_list(n): err for n, err in l} def _serialize_averaged_edge_gate_errors( - d: Optional[Dict[_Edge, float]] -) -> Optional[List[List]]: + d: dict[_Edge, float] | None +) -> list[list] | None: if d is None: return None return [[[n0.to_list(), n1.to_list()], err] for (n0, n1), err in d.items()] def _deserialize_averaged_edge_gate_errors( - l: Optional[List[List]], -) -> Optional[Dict[Tuple, float]]: + l: list[list] | None, +) -> dict[tuple, float] | None: if l is None: return None return {(Node.from_list(n0), Node.from_list(n1)): err for (n0, n1), err in l} def _serialize_averaged_readout_errors( - d: Optional[Dict[Node, float]] -) -> Optional[List[List]]: + d: dict[Node, float] | None +) -> list[list] | None: if d is None: return None return [[n.to_list(), err] for n, err in d.items()] def _deserialize_averaged_readout_errors( - l: Optional[List[List]], -) -> Optional[Dict[Node, float]]: + l: list[list] | None, +) -> dict[Node, float] | None: if l is None: return None return {Node.from_list(n): err for n, err in l} @@ -169,30 +167,30 @@ class BackendInfo: # identifying information name: str - device_name: Optional[str] + device_name: str | None version: str # hardware constraints - architecture: Optional[Union[Architecture, FullyConnected]] - gate_set: Set[OpType] - n_cl_reg: Optional[int] = None + architecture: Architecture | FullyConnected | None + gate_set: set[OpType] + n_cl_reg: int | None = None # additional feature support supports_fast_feedforward: bool = False supports_reset: bool = False supports_midcircuit_measurement: bool = False # additional basic device characterisation information - all_node_gate_errors: Optional[Dict[Node, Dict[OpType, float]]] = None - all_edge_gate_errors: Optional[Dict[Tuple[Node, Node], Dict[OpType, float]]] = None - all_readout_errors: Optional[Dict[Node, List[List[float]]]] = None - averaged_node_gate_errors: Optional[Dict[Node, float]] = None - averaged_edge_gate_errors: Optional[Dict[Tuple[Node, Node], float]] = None - averaged_readout_errors: Optional[Dict[Node, float]] = None + all_node_gate_errors: dict[Node, dict[OpType, float]] | None = None + all_edge_gate_errors: dict[tuple[Node, Node], dict[OpType, float]] | None = None + all_readout_errors: dict[Node, list[list[float]]] | None = None + averaged_node_gate_errors: dict[Node, float] | None = None + averaged_edge_gate_errors: dict[tuple[Node, Node], float] | None = None + averaged_readout_errors: dict[Node, float] | None = None # miscellaneous, eg additional noise characterisation and provider-supplied # information - misc: Dict[str, Any] = field(default_factory=dict) + misc: dict[str, Any] = field(default_factory=dict) @property - def nodes(self) -> List[Node]: + def nodes(self) -> list[Node]: """ List of device nodes of the backend. Returns empty list if the `architecture` field is not provided. @@ -240,7 +238,7 @@ def get_misc(self, key: str) -> Any: """ return self.misc[key] - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """ Generate a dictionary serialized representation of BackendInfo, suitable for writing to JSON. @@ -274,7 +272,7 @@ def to_dict(self) -> Dict[str, Any]: return self_dict @classmethod - def from_dict(cls, d: Dict[str, Any]) -> "BackendInfo": + def from_dict(cls, d: dict[str, Any]) -> "BackendInfo": """ Construct BackendInfo object from JSON serializable dictionary representation, as generated by BackendInfo.to_dict. @@ -313,10 +311,10 @@ def from_dict(cls, d: Dict[str, Any]) -> "BackendInfo": def fully_connected_backendinfo( # type: ignore name: str, - device_name: Optional[str], + device_name: str | None, version: str, n_nodes: int, - gate_set: Set[OpType], + gate_set: set[OpType], **kwargs ) -> BackendInfo: """ diff --git a/pytket/pytket/backends/backendresult.py b/pytket/pytket/backends/backendresult.py index 95ef954b1b..d2d7794afa 100644 --- a/pytket/pytket/backends/backendresult.py +++ b/pytket/pytket/backends/backendresult.py @@ -13,44 +13,32 @@ # limitations under the License. """`BackendResult` class and associated methods.""" -from typing import ( - Optional, - Any, - Sequence, - Iterable, - List, - Tuple, - Dict, - Counter, - NamedTuple, - Collection, - Type, - TypeVar, - cast, -) import operator -from functools import reduce import warnings +from collections import Counter +from collections.abc import Collection, Iterable, Sequence +from functools import reduce +from typing import Any, NamedTuple, TypeVar, cast import numpy as np from pytket.circuit import ( + _DEBUG_ONE_REG_PREFIX, + _DEBUG_ZERO_REG_PREFIX, BasisOrder, Bit, Circuit, Qubit, UnitID, - _DEBUG_ZERO_REG_PREFIX, - _DEBUG_ONE_REG_PREFIX, ) from pytket.utils.distribution import EmpiricalDistribution, ProbabilityDistribution +from pytket.utils.outcomearray import OutcomeArray, readout_counts from pytket.utils.results import ( - probs_from_state, get_n_qb_from_statevector, permute_basis_indexing, permute_rows_cols_in_unitary, + probs_from_state, ) -from pytket.utils.outcomearray import OutcomeArray, readout_counts from .backend_exceptions import InvalidResultType @@ -58,11 +46,11 @@ class StoredResult(NamedTuple): """NamedTuple with optional fields for all result types.""" - counts: Optional[Counter[OutcomeArray]] = None - shots: Optional[OutcomeArray] = None - state: Optional[np.ndarray] = None - unitary: Optional[np.ndarray] = None - density_matrix: Optional[np.ndarray] = None + counts: Counter[OutcomeArray] | None = None + shots: OutcomeArray | None = None + state: np.ndarray | None = None + unitary: np.ndarray | None = None + density_matrix: np.ndarray | None = None class BackendResult: @@ -89,14 +77,14 @@ class BackendResult: def __init__( self, *, - q_bits: Optional[Sequence[Qubit]] = None, - c_bits: Optional[Sequence[Bit]] = None, - counts: Optional[Counter[OutcomeArray]] = None, - shots: Optional[OutcomeArray] = None, + q_bits: Sequence[Qubit] | None = None, + c_bits: Sequence[Bit] | None = None, + counts: Counter[OutcomeArray] | None = None, + shots: OutcomeArray | None = None, state: Any = None, unitary: Any = None, density_matrix: Any = None, - ppcirc: Optional[Circuit] = None, + ppcirc: Circuit | None = None, ): # deal with mutable defaults if q_bits is None: @@ -113,20 +101,18 @@ def __init__( self._ppcirc = ppcirc - self.c_bits: Dict[Bit, int] = dict() - self.q_bits: Dict[Qubit, int] = dict() + self.c_bits: dict[Bit, int] = dict() + self.q_bits: dict[Qubit, int] = dict() def _process_unitids( - var: Sequence[UnitID], attr: str, lent: int, uid: Type[UnitID] + var: Sequence[UnitID], attr: str, lent: int, uid: type[UnitID] ) -> None: if var: setattr(self, attr, dict((unit, i) for i, unit in enumerate(var))) if lent != len(var): raise ValueError( - ( - f"Length of {attr} ({len(var)}) does not" - f" match input data dimensions ({lent})." - ) + f"Length of {attr} ({len(var)}) does not" + f" match input data dimensions ({lent})." ) else: setattr(self, attr, dict((uid(i), i) for i in range(lent))) # type: ignore @@ -161,9 +147,9 @@ def _process_unitids( def __repr__(self) -> str: return ( - "BackendResult(q_bits={s.q_bits},c_bits={s.c_bits},counts={s._counts}," - "shots={s._shots},state={s._state},unitary={s._unitary}," - "density_matrix={s._density_matrix})".format(s=self) + f"BackendResult(q_bits={self.q_bits},c_bits={self.c_bits}," + f"counts={self._counts},shots={self._shots},state={self._state}," + f"unitary={self._unitary},density_matrix={self._density_matrix})" ) @property @@ -197,7 +183,7 @@ def __eq__(self, other: object) -> bool: and np.array_equal(self._density_matrix, other._density_matrix) ) - def get_bitlist(self) -> List[Bit]: + def get_bitlist(self) -> list[Bit]: """Return list of Bits in internal storage order. :raises AttributeError: BackendResult does not include a Bits list. @@ -206,7 +192,7 @@ def get_bitlist(self) -> List[Bit]: """ return _sort_keys_by_val(self.c_bits) - def get_qbitlist(self) -> List[Qubit]: + def get_qbitlist(self) -> list[Qubit]: """Return list of Qubits in internal storage order. :raises AttributeError: BackendResult does not include a Qubits list. @@ -217,9 +203,9 @@ def get_qbitlist(self) -> List[Qubit]: return _sort_keys_by_val(self.q_bits) def _get_measured_res( - self, bits: Sequence[Bit], ppcirc: Optional[Circuit] = None + self, bits: Sequence[Bit], ppcirc: Circuit | None = None ) -> StoredResult: - vals: Dict[str, Any] = {} + vals: dict[str, Any] = {} if not self.contains_measured_results: raise InvalidResultType("shots/counts") @@ -281,7 +267,7 @@ def _get_measured_res( def _permute_statearray_qb_labels( self, array: np.ndarray, - relabling_map: Dict[Qubit, Qubit], + relabling_map: dict[Qubit, Qubit], ) -> np.ndarray: """Permute statevector/unitary according to a relabelling of Qubits. @@ -292,7 +278,7 @@ def _permute_statearray_qb_labels( :return: Permuted array. :rtype: np.ndarray """ - original_labeling: Sequence["Qubit"] = self.get_qbitlist() + original_labeling: Sequence[Qubit] = self.get_qbitlist() n_labels = len(original_labeling) permutation = [0] * n_labels for i, orig_qb in enumerate(original_labeling): @@ -308,7 +294,7 @@ def _permute_statearray_qb_labels( return permuter(array, tuple(permutation)) def _get_state_res(self, qubits: Sequence[Qubit]) -> StoredResult: - vals: Dict[str, Any] = {} + vals: dict[str, Any] = {} if not self.contains_state_results: raise InvalidResultType("state/unitary/density_matrix") @@ -332,9 +318,9 @@ def _get_state_res(self, qubits: Sequence[Qubit]) -> StoredResult: def get_result( self, - request_ids: Optional[Sequence[UnitID]] = None, + request_ids: Sequence[UnitID] | None = None, basis: BasisOrder = BasisOrder.ilo, - ppcirc: Optional[Circuit] = None, + ppcirc: Circuit | None = None, ) -> StoredResult: """Retrieve all results, optionally according to a specified UnitID ordering or subset. @@ -384,9 +370,9 @@ def get_result( def get_shots( self, - cbits: Optional[Sequence[Bit]] = None, + cbits: Sequence[Bit] | None = None, basis: BasisOrder = BasisOrder.ilo, - ppcirc: Optional[Circuit] = None, + ppcirc: Circuit | None = None, ) -> np.ndarray: """Return shots if available. @@ -413,10 +399,10 @@ def get_shots( def get_counts( self, - cbits: Optional[Sequence[Bit]] = None, + cbits: Sequence[Bit] | None = None, basis: BasisOrder = BasisOrder.ilo, - ppcirc: Optional[Circuit] = None, - ) -> Counter[Tuple[int, ...]]: + ppcirc: Circuit | None = None, + ) -> Counter[tuple[int, ...]]: """Return counts of outcomes if available. :param cbits: ordered subset of Bits, returns all results by default, defaults @@ -441,7 +427,7 @@ def get_counts( def get_state( self, - qbits: Optional[Sequence[Qubit]] = None, + qbits: Sequence[Qubit] | None = None, basis: BasisOrder = BasisOrder.ilo, ) -> np.ndarray: """Return statevector if available. @@ -467,7 +453,7 @@ def get_state( def get_unitary( self, - qbits: Optional[Sequence[Qubit]] = None, + qbits: Sequence[Qubit] | None = None, basis: BasisOrder = BasisOrder.ilo, ) -> np.ndarray: """Return unitary if available. @@ -490,7 +476,7 @@ def get_unitary( def get_density_matrix( self, - qbits: Optional[Sequence[Qubit]] = None, + qbits: Sequence[Qubit] | None = None, basis: BasisOrder = BasisOrder.ilo, ) -> np.ndarray: """Return density_matrix if available. @@ -512,8 +498,8 @@ def get_density_matrix( raise InvalidResultType("density_matrix") def get_distribution( - self, units: Optional[Sequence[UnitID]] = None - ) -> Dict[Tuple[int, ...], float]: + self, units: Sequence[UnitID] | None = None + ) -> dict[tuple[int, ...], float]: """Calculate an exact or approximate probability distribution over outcomes. If the exact statevector is known, the exact probability distribution is @@ -543,12 +529,11 @@ def get_distribution( except InvalidResultType: counts = self.get_counts(units) # type: ignore total = sum(counts.values()) - dist = {outcome: count / total for outcome, count in counts.items()} - return dist + return {outcome: count / total for outcome, count in counts.items()} def get_empirical_distribution( - self, bits: Optional[Sequence[Bit]] = None - ) -> EmpiricalDistribution[Tuple[int, ...]]: + self, bits: Sequence[Bit] | None = None + ) -> EmpiricalDistribution[tuple[int, ...]]: """Convert to a :py:class:`pytket.utils.distribution.EmpiricalDistribution` where the observations are sequences of 0s and 1s. @@ -563,8 +548,8 @@ def get_empirical_distribution( return EmpiricalDistribution(self.get_counts(bits)) def get_probability_distribution( - self, qubits: Optional[Sequence[Qubit]] = None, min_p: float = 0.0 - ) -> ProbabilityDistribution[Tuple[int, ...]]: + self, qubits: Sequence[Qubit] | None = None, min_p: float = 0.0 + ) -> ProbabilityDistribution[tuple[int, ...]]: """Convert to a :py:class:`pytket.utils.distribution.ProbabilityDistribution` where the possible outcomes are sequences of 0s and 1s. @@ -582,7 +567,7 @@ def get_probability_distribution( state = self.get_state(qubits) return ProbabilityDistribution(probs_from_state(state), min_p=min_p) - def get_debug_info(self) -> Dict[str, float]: + def get_debug_info(self) -> dict[str, float]: """Calculate the success rate of each assertion averaged across shots. Each assertion in pytket is decomposed into a sequence of transformations @@ -594,7 +579,7 @@ def get_debug_info(self) -> Dict[str, float]: """ _tket_debug_zero_prefix = _DEBUG_ZERO_REG_PREFIX + "_" _tket_debug_one_prefix = _DEBUG_ONE_REG_PREFIX + "_" - debug_bit_dict: Dict[str, Dict[str, Any]] = {} + debug_bit_dict: dict[str, dict[str, Any]] = {} for bit in self.c_bits: if bit.reg_name.startswith(_tket_debug_zero_prefix): expectation = 0 @@ -609,7 +594,7 @@ def get_debug_info(self) -> Dict[str, float]: debug_bit_dict[assertion_name]["bits"].append(bit) debug_bit_dict[assertion_name]["expectations"].append(expectation) - debug_result_dict: Dict[str, float] = {} + debug_result_dict: dict[str, float] = {} for assertion_name, bits_info in debug_bit_dict.items(): counts = self.get_counts(bits_info["bits"]) debug_result_dict[assertion_name] = counts[ @@ -617,14 +602,14 @@ def get_debug_info(self) -> Dict[str, float]: ] / sum(counts.values()) return debug_result_dict - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Generate a dictionary serialized representation of BackendResult, suitable for writing to JSON. :return: JSON serializable dictionary. :rtype: Dict[str, Any] """ - outdict: Dict[str, Any] = dict() + outdict: dict[str, Any] = dict() outdict["qubits"] = [q.to_list() for q in self.get_qbitlist()] outdict["bits"] = [c.to_list() for c in self.get_bitlist()] if self._shots is not None: @@ -645,7 +630,7 @@ def to_dict(self) -> Dict[str, Any]: return outdict @classmethod - def from_dict(cls, res_dict: Dict[str, Any]) -> "BackendResult": + def from_dict(cls, res_dict: dict[str, Any]) -> "BackendResult": """Construct BackendResult object from JSON serializable dictionary representation, as generated by BackendResult.to_dict. @@ -692,7 +677,7 @@ def from_dict(cls, res_dict: Dict[str, Any]) -> "BackendResult": T = TypeVar("T") -def _sort_keys_by_val(dic: Dict[T, int]) -> List[T]: +def _sort_keys_by_val(dic: dict[T, int]) -> list[T]: if not dic: return [] vals, _ = zip(*sorted(dic.items(), key=lambda x: x[1])) @@ -703,12 +688,12 @@ def _check_permuted_sequence(first: Collection[Any], second: Collection[Any]) -> return len(first) == len(second) and set(first) == set(second) -def _complex_ar_to_dict(ar: np.ndarray) -> Dict[str, List]: +def _complex_ar_to_dict(ar: np.ndarray) -> dict[str, list]: """Dictionary of real, imaginary parts of complex array, each in list form.""" return {"real": ar.real.tolist(), "imag": ar.imag.tolist()} -def _complex_ar_from_dict(dic: Dict[str, List]) -> np.ndarray: +def _complex_ar_from_dict(dic: dict[str, list]) -> np.ndarray: """Construct complex array from dictionary of real and imaginary parts""" out = np.array(dic["real"], dtype=complex) diff --git a/pytket/pytket/backends/resulthandle.py b/pytket/pytket/backends/resulthandle.py index 415ec82b77..7d44b014fd 100644 --- a/pytket/pytket/backends/resulthandle.py +++ b/pytket/pytket/backends/resulthandle.py @@ -15,14 +15,14 @@ """ResultHandle class """ -from typing import Tuple, Type, Union, Iterator, overload from ast import literal_eval -from collections.abc import Sequence +from collections.abc import Iterator, Sequence +from typing import Union, overload # mypy doesn't think you can pass the tuple to Union BasicHashType = Union[int, float, complex, str, bool, bytes] -_ResultIdTuple = Tuple[ - Union[Type[int], Type[float], Type[complex], Type[str], Type[bool], Type[bytes]], +_ResultIdTuple = tuple[ + type[int] | type[float] | type[complex] | type[str] | type[bool] | type[bytes], ..., ] @@ -86,11 +86,11 @@ def __len__(self) -> int: def __getitem__(self, key: int) -> BasicHashType: ... @overload - def __getitem__(self, key: slice) -> Tuple[BasicHashType, ...]: ... + def __getitem__(self, key: slice) -> tuple[BasicHashType, ...]: ... def __getitem__( - self, key: Union[int, slice] - ) -> Union[BasicHashType, Tuple[BasicHashType, ...]]: + self, key: int | slice + ) -> BasicHashType | tuple[BasicHashType, ...]: # weird logic required to make mypy happy, can't just # return self._identifiers[key] if isinstance(key, slice): diff --git a/pytket/pytket/backends/status.py b/pytket/pytket/backends/status.py index 022f6387d6..543f1d0ad2 100644 --- a/pytket/pytket/backends/status.py +++ b/pytket/pytket/backends/status.py @@ -14,9 +14,10 @@ """Status classes for circuits submitted to backends. """ +from collections.abc import Callable from datetime import datetime -from typing import Any, Callable, Dict, NamedTuple, Optional from enum import Enum +from typing import Any, NamedTuple class StatusEnum(Enum): @@ -43,21 +44,21 @@ class CircuitStatus(NamedTuple): status: StatusEnum message: str = "" - error_detail: Optional[str] = None + error_detail: str | None = None # Timestamp for when a status was last entered. - completed_time: Optional[datetime] = None - queued_time: Optional[datetime] = None - submitted_time: Optional[datetime] = None - running_time: Optional[datetime] = None - cancelled_time: Optional[datetime] = None - error_time: Optional[datetime] = None + completed_time: datetime | None = None + queued_time: datetime | None = None + submitted_time: datetime | None = None + running_time: datetime | None = None + cancelled_time: datetime | None = None + error_time: datetime | None = None - queue_position: Optional[int] = None + queue_position: int | None = None - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Return JSON serializable dictionary representation.""" - circuit_status_dict: Dict[str, Any] = { + circuit_status_dict: dict[str, Any] = { "status": self.status.name, "message": self.message, } @@ -83,7 +84,7 @@ def to_dict(self) -> Dict[str, Any]: return circuit_status_dict @classmethod - def from_dict(cls, dic: Dict[str, Any]) -> "CircuitStatus": + def from_dict(cls, dic: dict[str, Any]) -> "CircuitStatus": """Construct from JSON serializable dictionary.""" invalid = ValueError(f"Dictionary invalid format for CircuitStatus: {dic}") if "message" not in dic or "status" not in dic: @@ -94,9 +95,9 @@ def from_dict(cls, dic: Dict[str, Any]) -> "CircuitStatus": except StopIteration as e: raise invalid from e - error_detail = dic.get("error_detail", None) + error_detail = dic.get("error_detail") - read_optional_datetime: Callable[[str], Optional[datetime]] = lambda key: ( + read_optional_datetime: Callable[[str], datetime | None] = lambda key: ( datetime.fromisoformat(x) if (x := dic.get(key)) is not None else None ) completed_time = read_optional_datetime("completed_time") @@ -106,7 +107,7 @@ def from_dict(cls, dic: Dict[str, Any]) -> "CircuitStatus": cancelled_time = read_optional_datetime("cancelled_time") error_time = read_optional_datetime("error_time") - queue_position = dic.get("queue_position", None) + queue_position = dic.get("queue_position") return cls( status, diff --git a/pytket/pytket/circuit/__init__.py b/pytket/pytket/circuit/__init__.py index 6040d896a0..3a3d799a15 100644 --- a/pytket/pytket/circuit/__init__.py +++ b/pytket/pytket/circuit/__init__.py @@ -16,37 +16,38 @@ tket :py:class:`Circuit` data structure. This module is provided in binary form during the PyPI installation.""" from typing import ( - TYPE_CHECKING, Any, - Tuple, - Type, - Union, Callable, Optional, Sequence, + Union, ) +from pytket import wasm from pytket._tket.circuit import * from pytket._tket.circuit import Circuit - +from pytket._tket.pauli import Pauli from pytket._tket.unit_id import * -from pytket._tket.unit_id import Bit, BitRegister # prefixes for assertion bits -from pytket._tket.unit_id import _DEBUG_ZERO_REG_PREFIX, _DEBUG_ONE_REG_PREFIX -from pytket._tket.pauli import Pauli - -from pytket import wasm +from pytket._tket.unit_id import ( + _DEBUG_ONE_REG_PREFIX, + _DEBUG_ZERO_REG_PREFIX, + Bit, + BitRegister, +) from .logic_exp import ( + BinaryOp, BitLogicExp, BitWiseOp, - RegLogicExp, - RegWiseOp, Constant, LogicExp, - BinaryOp, Ops, + RegLogicExp, + RegWiseOp, + create_bit_logic_exp, + create_reg_logic_exp, if_bit, if_not_bit, reg_eq, @@ -55,8 +56,6 @@ reg_leq, reg_lt, reg_neq, - create_reg_logic_exp, - create_bit_logic_exp, ) diff --git a/pytket/pytket/circuit/add_condition.py b/pytket/pytket/circuit/add_condition.py index 79abac5209..d48a825949 100644 --- a/pytket/pytket/circuit/add_condition.py +++ b/pytket/pytket/circuit/add_condition.py @@ -13,25 +13,21 @@ # limitations under the License. """Enable adding of gates with conditions on Bit or BitRegister expressions.""" -from typing import Tuple, Union -from pytket.circuit import Bit, Circuit, BitRegister -from pytket._tket.unit_id import ( - _TEMP_REG_SIZE, - _TEMP_BIT_NAME, - _TEMP_BIT_REG_BASE, -) +from pytket._tket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE +from pytket.circuit import Bit, BitRegister, Circuit +from pytket.circuit.clexpr import wired_clexpr_from_logic_exp from pytket.circuit.logic_exp import ( BitLogicExp, Constant, PredicateExp, RegEq, - RegNeq, RegGeq, RegGt, RegLeq, RegLogicExp, RegLt, + RegNeq, ) @@ -40,14 +36,14 @@ class NonConstError(Exception): def _add_condition( - circ: Circuit, condition: Union[PredicateExp, Bit, BitLogicExp] -) -> Tuple[Bit, bool]: + circ: Circuit, condition: PredicateExp | Bit | BitLogicExp +) -> tuple[Bit, bool]: """Add a condition expression to a circuit using classical expression boxes, rangepredicates and conditionals. Return predicate bit and value of said bit. """ if isinstance(condition, Bit): return condition, True - elif isinstance(condition, PredicateExp): + if isinstance(condition, PredicateExp): pred_exp, pred_val = condition.args # PredicateExp constructor should ensure arg order if not isinstance(pred_val, Constant): @@ -60,7 +56,7 @@ def _add_condition( pred_exp = condition else: raise ValueError( - f"Condition {condition} must be of type Bit, " "BitLogicExp or PredicateExp" + f"Condition {condition} must be of type Bit, BitLogicExp or PredicateExp" ) next_index = ( @@ -79,7 +75,8 @@ def _add_condition( circ.add_bit(condition_bit) if isinstance(pred_exp, BitLogicExp): - circ.add_classicalexpbox_bit(pred_exp, [condition_bit]) + wexpr, args = wired_clexpr_from_logic_exp(pred_exp, [condition_bit]) + circ.add_clexpr(wexpr, args) return condition_bit, bool(pred_val) assert isinstance(pred_exp, (RegLogicExp, BitRegister)) @@ -99,10 +96,11 @@ def _add_condition( int(r_name.split("_")[-1]) for r_name in existing_reg_names ) next_index = max(existing_reg_indices, default=-1) + 1 - temp_reg = BitRegister(f"{_TEMP_BIT_REG_BASE}_{next_index}", _TEMP_REG_SIZE) + temp_reg = BitRegister(f"{_TEMP_BIT_REG_BASE}_{next_index}", min_reg_size) circ.add_c_register(temp_reg) - target_bits = temp_reg.to_list()[:min_reg_size] - circ.add_classicalexpbox_register(pred_exp, target_bits) + target_bits = temp_reg.to_list() + wexpr, args = wired_clexpr_from_logic_exp(pred_exp, target_bits) + circ.add_clexpr(wexpr, args) elif isinstance(pred_exp, BitRegister): target_bits = pred_exp.to_list() diff --git a/pytket/pytket/circuit/clexpr.py b/pytket/pytket/circuit/clexpr.py index b6059c560c..62d7e4bfe1 100644 --- a/pytket/pytket/circuit/clexpr.py +++ b/pytket/pytket/circuit/clexpr.py @@ -13,16 +13,21 @@ # limitations under the License. from dataclasses import dataclass +from typing import Any + from pytket.circuit import ( Bit, BitRegister, + Circuit, ClBitVar, ClExpr, + ClExprOp, ClOp, ClRegVar, + OpType, WiredClExpr, ) -from pytket.circuit.logic_exp import Ops, BitWiseOp, RegWiseOp, LogicExp +from pytket.circuit.logic_exp import BitWiseOp, LogicExp, Ops, RegWiseOp _reg_output_clops = set( [ @@ -161,3 +166,39 @@ def wired_clexpr_from_logic_exp( ), args, ) + + +def check_register_alignments(circ: Circuit) -> bool: + """Check whether all `ClExprOp` operations in the circuit are register-aligned. + + This means that all register variables and outputs occurring in `ClExprOp` comprise + whole registers with the bits in the correct order. + + :param circ: circuit to check + :return: True iff all `ClExprOp` operations are register-aligned + """ + cregs: set[tuple[Bit, ...]] = set( + tuple(creg.to_list()) for creg in circ.c_registers + ) + for cmd in circ: + op = cmd.op + if op.type == OpType.ClExpr: + assert isinstance(op, ClExprOp) + wexpr: WiredClExpr = op.expr + args = cmd.args + if any( + tuple(args[i] for i in poslist) not in cregs + for poslist in wexpr.reg_posn.values() + ) or ( + has_reg_output(wexpr.expr.op) + and tuple(args[i] for i in wexpr.output_posn) not in cregs + ): + return False + return True + + +def _add_clexpr_to_circuit_from_logicexp( + circ: Circuit, exp: LogicExp, output_bits: list[Bit], **kwargs: Any +) -> None: + wexpr, args = wired_clexpr_from_logic_exp(exp, output_bits) + circ.add_clexpr(wexpr, args, **kwargs) diff --git a/pytket/pytket/circuit/decompose_classical.py b/pytket/pytket/circuit/decompose_classical.py index 1b525bc4f1..11a7f1a71c 100644 --- a/pytket/pytket/circuit/decompose_classical.py +++ b/pytket/pytket/circuit/decompose_classical.py @@ -15,28 +15,30 @@ """Functions for decomposing Circuits containing classical expressions in to primitive logical operations.""" import copy +from collections.abc import Callable from heapq import heappop, heappush -from typing import ( - Callable, - Dict, - List, - Optional, - Set, - Tuple, - Type, - TypeVar, - Union, - Generic, +from typing import Any, Generic, TypeVar + +from pytket._tket.circuit import ( + Circuit, + ClassicalExpBox, + ClBitVar, + ClExpr, + ClExprOp, + ClOp, + ClRegVar, + Conditional, + OpType, + WiredClExpr, ) - from pytket._tket.unit_id import ( _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE, _TEMP_REG_SIZE, - BitRegister, Bit, + BitRegister, ) -from pytket._tket.circuit import Circuit, ClassicalExpBox, Conditional, OpType +from pytket.circuit.clexpr import check_register_alignments, has_reg_output from pytket.circuit.logic_exp import ( BitLogicExp, BitWiseOp, @@ -58,8 +60,8 @@ class VarHeap(Generic[T]): """A generic heap implementation.""" def __init__(self) -> None: - self._heap: List[T] = [] - self._heap_vars: Set[T] = set() + self._heap: list[T] = [] + self._heap_vars: set[T] = set() def pop(self) -> T: """Pop from top of heap.""" @@ -137,19 +139,18 @@ def fresh_var(self, size: int = _TEMP_REG_SIZE) -> BitRegister: return new_reg -def temp_reg_in_args(args: List[Bit]) -> Optional[BitRegister]: +def temp_reg_in_args(args: list[Bit]) -> BitRegister | None: """If there are bits from a temporary register in the args, return it.""" temp_reg_bits = [b for b in args if b.reg_name.startswith(_TEMP_BIT_REG_BASE)] if temp_reg_bits: - temp_reg = BitRegister(temp_reg_bits[0].reg_name, _TEMP_REG_SIZE) - return temp_reg + return BitRegister(temp_reg_bits[0].reg_name, _TEMP_REG_SIZE) return None -VarType = TypeVar("VarType", Type[Bit], Type[BitRegister]) +VarType = TypeVar("VarType", type[Bit], type[BitRegister]) -def int_to_bools(val: Constant, width: int) -> List[bool]: +def int_to_bools(val: Constant, width: int) -> list[bool]: # map int to bools via litle endian encoding return list(map(bool, map(int, reversed(f"{val:0{width}b}"[-width:])))) @@ -164,7 +165,7 @@ def get_bit_width(x: int) -> int: def _gen_walk(var_type: VarType, newcirc: Circuit, heap: VarHeap) -> Callable[ - [Union[RegLogicExp, BitLogicExp], Optional[Variable], Optional[Dict]], + [RegLogicExp | BitLogicExp, Variable | None, dict | None], Variable, ]: """Generate a recursive walk method for decomposing an expression tree.""" @@ -178,8 +179,8 @@ def _gen_walk(var_type: VarType, newcirc: Circuit, heap: VarHeap) -> Callable[ RegWiseOp.XOR: newcirc.add_c_xor_to_registers, } if var_type is Bit: - op_type: Union[Type[BitWiseOp], Type[RegWiseOp]] = BitWiseOp - exp_type: Union[Type[BitLogicExp], Type[RegLogicExp]] = BitLogicExp + op_type: type[BitWiseOp] | type[RegWiseOp] = BitWiseOp + exp_type: type[BitLogicExp] | type[RegLogicExp] = BitLogicExp else: assert var_type is BitRegister op_type = RegWiseOp @@ -194,7 +195,7 @@ def add_method(var: Variable) -> None: newcirc.add_bit(var.__getitem__(i), reject_dups=False) # method for setting bits during walk - def set_bits(var: Variable, val: Constant, kwargs: Dict) -> None: + def set_bits(var: Variable, val: Constant, kwargs: dict) -> None: if isinstance(var, Bit): newcirc.add_c_setbits([bool(val)], [var], **kwargs) else: @@ -210,9 +211,9 @@ def set_bits(var: Variable, val: Constant, kwargs: Dict) -> None: # convert an expression to gates on the circuit # and return the variable holding the result def recursive_walk( - exp: Union[RegLogicExp, BitLogicExp], - targ_bit: Optional[Variable] = None, - kwargs: Optional[Dict] = None, + exp: RegLogicExp | BitLogicExp, + targ_bit: Variable | None = None, + kwargs: dict | None = None, ) -> Variable: assert isinstance(exp.op, op_type) kwargs = kwargs or {} @@ -253,8 +254,131 @@ def recursive_walk( return recursive_walk -def _decompose_expressions(circ: Circuit) -> Tuple[Circuit, bool]: - """Rewrite a circuit command-wise, decomposing ClassicalExpBox.""" +class ClExprDecomposer: + def __init__( + self, + circ: Circuit, + bit_posn: dict[int, int], + reg_posn: dict[int, list[int]], + args: list[Bit], + bit_heap: BitHeap, + reg_heap: RegHeap, + kwargs: dict[str, Any], + ): + self.circ: Circuit = circ + self.bit_posn: dict[int, int] = bit_posn + self.reg_posn: dict[int, list[int]] = reg_posn + self.args: list[Bit] = args + self.bit_heap: BitHeap = bit_heap + self.reg_heap: RegHeap = reg_heap + self.kwargs: dict[str, Any] = kwargs + # Construct maps from int (i.e. ClBitVar) to Bit, and from int (i.e. ClRegVar) + # to BitRegister: + self.bit_vars = {i: args[p] for i, p in bit_posn.items()} + self.reg_vars = { + i: BitRegister(args[p[0]].reg_name, len(p)) for i, p in reg_posn.items() + } + + def add_var(self, var: Variable) -> None: + """Add a Bit or BitRegister to the circuit if not already present.""" + if isinstance(var, Bit): + self.circ.add_bit(var, reject_dups=False) + else: + assert isinstance(var, BitRegister) + for bit in var.to_list(): + self.circ.add_bit(bit, reject_dups=False) + + def set_bits(self, var: Variable, val: int) -> None: + """Set the value of a Bit or BitRegister.""" + assert val >= 0 + if isinstance(var, Bit): + assert val >> 1 == 0 + self.circ.add_c_setbits([bool(val)], [var], **self.kwargs) + else: + assert isinstance(var, BitRegister) + assert val >> var.size == 0 + self.circ.add_c_setreg(val, var, **self.kwargs) + + def decompose_expr(self, expr: ClExpr, out_var: Variable | None) -> Variable: + """Add the decomposed expression to the circuit and return the Bit or + BitRegister that contains the result. + + :param expr: the expression to decompose + :param out_var: where to put the output (if None, create a new scratch location) + """ + op: ClOp = expr.op + heap: VarHeap = self.reg_heap if has_reg_output(op) else self.bit_heap + + # Eliminate (recursively) subsidiary expressions from the arguments, and convert + # all terms to Bit or BitRegister: + terms: list[Variable] = [] + for arg in expr.args: + if isinstance(arg, int): + # Assign to a fresh variable + fresh_var = heap.fresh_var() + self.add_var(fresh_var) + self.set_bits(fresh_var, arg) + terms.append(fresh_var) + elif isinstance(arg, ClBitVar): + terms.append(self.bit_vars[arg.index]) + elif isinstance(arg, ClRegVar): + terms.append(self.reg_vars[arg.index]) + else: + assert isinstance(arg, ClExpr) + terms.append(self.decompose_expr(arg, None)) + + # Enable reuse of temporary terms: + for term in terms: + if heap.is_heap_var(term): + heap.push(term) + + if out_var is None: + out_var = heap.fresh_var() + self.add_var(out_var) + match op: + case ClOp.BitAnd: + self.circ.add_c_and(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.BitNot: + self.circ.add_c_not(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.BitOne: + assert isinstance(out_var, Bit) + self.circ.add_c_setbits([True], [out_var], **self.kwargs) + case ClOp.BitOr: + self.circ.add_c_or(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.BitXor: + self.circ.add_c_xor(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.BitZero: + assert isinstance(out_var, Bit) + self.circ.add_c_setbits([False], [out_var], **self.kwargs) + case ClOp.RegAnd: + self.circ.add_c_and_to_registers(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.RegNot: + self.circ.add_c_not_to_registers(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.RegOne: + assert isinstance(out_var, BitRegister) + self.circ.add_c_setbits( + [True] * out_var.size, out_var.to_list(), **self.kwargs + ) + case ClOp.RegOr: + self.circ.add_c_or_to_registers(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.RegXor: + self.circ.add_c_xor_to_registers(*terms, out_var, **self.kwargs) # type: ignore + case ClOp.RegZero: + assert isinstance(out_var, BitRegister) + self.circ.add_c_setbits( + [False] * out_var.size, out_var.to_list(), **self.kwargs + ) + case _: + raise DecomposeClassicalError( + f"{op} cannot be decomposed to TKET primitives." + ) + return out_var + + +def _decompose_expressions(circ: Circuit) -> tuple[Circuit, bool]: + """Rewrite a circuit command-wise, decomposing ClassicalExpBox and ClExprOp.""" + if not check_register_alignments(circ): + raise DecomposeClassicalError("Circuit contains non-register-aligned ClExprOp.") bit_heap = BitHeap() reg_heap = RegHeap() # add already used heap variables to heaps @@ -281,7 +405,7 @@ def _decompose_expressions(circ: Circuit) -> Tuple[Circuit, bool]: reg_recursive_walk = _gen_walk(BitRegister, newcirc, reg_heap) # targets of predicates that need to be relabelled - replace_targets: Dict[Variable, Variable] = dict() + replace_targets: dict[Variable, Variable] = dict() modified = False for command in circ: op = command.op @@ -354,9 +478,41 @@ def _decompose_expressions(circ: Circuit) -> Tuple[Circuit, bool]: replace_targets[out_reg] = comp_reg modified = True continue + + elif optype == OpType.ClExpr: + assert isinstance(op, ClExprOp) + wexpr: WiredClExpr = op.expr + expr: ClExpr = wexpr.expr + bit_posn = wexpr.bit_posn + reg_posn = wexpr.reg_posn + output_posn = wexpr.output_posn + assert len(output_posn) > 0 + output0 = args[output_posn[0]] + assert isinstance(output0, Bit) + out_var: Variable = ( + BitRegister(output0.reg_name, len(output_posn)) + if has_reg_output(expr.op) + else output0 + ) + decomposer = ClExprDecomposer( + newcirc, bit_posn, reg_posn, args, bit_heap, reg_heap, kwargs # type: ignore + ) + comp_var = decomposer.decompose_expr(expr, out_var) + if comp_var != out_var: + replace_targets[out_var] = comp_var + modified = True + continue + if optype == OpType.Barrier: # add_gate doesn't work for metaops newcirc.add_barrier(args) else: + for arg in args: + if ( + isinstance(arg, Bit) + and arg.reg_name != "_w" # workaround: this shouldn't be type Bit + and arg not in newcirc.bits + ): + newcirc.add_bit(arg) newcirc.add_gate(op, args, **kwargs) return newcirc, modified diff --git a/pytket/pytket/circuit/display/__init__.py b/pytket/pytket/circuit/display/__init__.py index 38483197c0..cc36129605 100644 --- a/pytket/pytket/circuit/display/__init__.py +++ b/pytket/pytket/circuit/display/__init__.py @@ -21,12 +21,12 @@ import uuid import webbrowser from dataclasses import dataclass, field -from typing import Literal, cast, Any +from typing import Any, Literal, cast -from jinja2 import Environment, PrefixLoader, FileSystemLoader, nodes +from jinja2 import Environment, FileSystemLoader, PrefixLoader, nodes from jinja2.ext import Extension -from jinja2.utils import markupsafe from jinja2.parser import Parser +from jinja2.utils import markupsafe from pytket.circuit import Circuit from pytket.config import PytketExtConfig @@ -47,8 +47,7 @@ def _render(self, filename: str) -> markupsafe.Markup: return markupsafe.Markup( self.environment.loader.get_source(self.environment, filename)[0] ) - else: - return markupsafe.Markup("") + return markupsafe.Markup("") # Set up jinja to access our templates diff --git a/pytket/pytket/circuit/logic_exp.py b/pytket/pytket/circuit/logic_exp.py index 9ba71c2317..a62f617a3e 100644 --- a/pytket/pytket/circuit/logic_exp.py +++ b/pytket/pytket/circuit/logic_exp.py @@ -14,30 +14,17 @@ """Classes and functions for constructing logical expressions over Bit and BitRegister.""" -from typing import ( - Any, - Iterable, - Set, - Tuple, - Type, - List, - Union, - Dict, - ClassVar, - Iterator, - TypeVar, - cast, - Sequence, -) -from enum import Enum +from collections.abc import Iterable, Iterator, Sequence from dataclasses import dataclass +from enum import Enum +from typing import Any, ClassVar, TypeVar, Union, cast from pytket.circuit import Bit, BitRegister T = TypeVar("T") -def filter_by_type(seq: Iterable, var_type: Type[T]) -> Iterator[Tuple[int, T]]: +def filter_by_type(seq: Iterable, var_type: type[T]) -> Iterator[tuple[int, T]]: """Return enumeration of seq, with only elements of type var_type.""" return filter(lambda x: isinstance(x[1], var_type), enumerate(seq)) @@ -75,7 +62,7 @@ class RegWiseOp(Enum): LSH = "<<" RSH = ">>" NOT = "~" - NEG = "-" + NEG = "-" # noqa: PIE796 Ops = Union[BitWiseOp, RegWiseOp] # all op enum types @@ -92,12 +79,12 @@ class LogicExp: Encoded as a tree of expressions""" op: Ops # enum for operation encoded by this node - args: List[ArgType] # arguments of operation + args: list[ArgType] # arguments of operation # class level dictionary mapping enum to class - op_cls_dict: ClassVar[Dict[Ops, Type["LogicExp"]]] = dict() + op_cls_dict: ClassVar[dict[Ops, type["LogicExp"]]] = dict() @classmethod - def factory(cls, op: Ops) -> Type["LogicExp"]: + def factory(cls, op: Ops) -> type["LogicExp"]: """Return matching operation class for enum.""" # RegNeg cannot be initialised this way as "-" clashes with SUB if op == BitWiseOp.AND: @@ -162,7 +149,7 @@ def set_value(self, var: Variable, val: Constant) -> None: arg.set_value(var, val) @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: """Evaluate expression given constant values for all args.""" raise NotImplementedError @@ -173,17 +160,17 @@ def eval_vals(self) -> ArgType: self.args[i] = arg.eval_vals() if all(isinstance(a, Constant) for a in self.args): try: - rval = self._const_eval(cast(List[Constant], self.args)) + rval = self._const_eval(cast(list[Constant], self.args)) except NotImplementedError: pass return rval - def all_inputs(self) -> Set[Variable]: + def all_inputs(self) -> set[Variable]: """ :return: All variables involved in expression. :rtype: Set[Variable] """ - outset: Set[Variable] = set() + outset: set[Variable] = set() for arg in self.args: if isinstance(arg, LogicExp): @@ -192,9 +179,8 @@ def all_inputs(self) -> Set[Variable]: if isinstance(self, BitLogicExp): if isinstance(arg, Bit): outset.add(arg) - else: - if isinstance(arg, BitRegister): - outset.add(arg) + elif isinstance(arg, BitRegister): + outset.add(arg) return outset def all_inputs_ordered(self) -> list[Variable]: @@ -212,9 +198,8 @@ def all_inputs_ordered(self) -> list[Variable]: if isinstance(self, BitLogicExp): if isinstance(arg, Bit): outset[arg] = None - else: - if isinstance(arg, BitRegister): - outset[arg] = None + elif isinstance(arg, BitRegister): + outset[arg] = None return list(outset) def __eq__(self, other: object) -> bool: @@ -222,10 +207,10 @@ def __eq__(self, other: object) -> bool: return False return (self.op == other.op) and (self.args == other.args) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Output JSON serializable nested dictionary.""" - out: Dict[str, Any] = {"op": str(self.op)} - args_ser: List[Union[Dict, Constant, List[Union[str, int]]]] = [] + out: dict[str, Any] = {"op": str(self.op)} + args_ser: list[dict | Constant | list[str | int]] = [] for arg in self.args: if isinstance(arg, LogicExp): @@ -241,18 +226,18 @@ def to_dict(self) -> Dict[str, Any]: return out @classmethod - def from_dict(cls, dic: Dict[str, Any]) -> "LogicExp": + def from_dict(cls, dic: dict[str, Any]) -> "LogicExp": """Load from JSON serializable nested dictionary.""" opset_name, op_name = dic["op"].split(".", 2) opset = BitWiseOp if opset_name == "BitWiseOp" else RegWiseOp op = next(o for o in opset if o.name == op_name) - args: List[ArgType] = [] + args: list[ArgType] = [] for arg_ser in dic["args"]: if isinstance(arg_ser, Constant): args.append(arg_ser) - elif isinstance(arg_ser, List): + elif isinstance(arg_ser, list): args.append(Bit(arg_ser[0], arg_ser[1])) - elif isinstance(arg_ser, Dict): + elif isinstance(arg_ser, dict): if "op" in arg_ser: args.append(LogicExp.from_dict(arg_ser)) else: @@ -260,7 +245,7 @@ def from_dict(cls, dic: Dict[str, Any]) -> "LogicExp": return create_logic_exp(op, args) def _rename_args_recursive( - self, cmap: Dict[Bit, Bit], renamed_regs: Set[str] + self, cmap: dict[Bit, Bit], renamed_regs: set[str] ) -> bool: success = False for i, arg in enumerate(self.args): @@ -279,13 +264,13 @@ def _rename_args_recursive( success |= arg._rename_args_recursive(cmap, renamed_regs) return success - def rename_args(self, cmap: Dict[Bit, Bit]) -> bool: + def rename_args(self, cmap: dict[Bit, Bit]) -> bool: """Rename the Bits according to a Bit map. Raise ValueError if a bit is being used in a register-wise expression. """ if all(old_bit == new_bit for old_bit, new_bit in cmap.items()): return False - renamed_regs = set([key.reg_name for key in cmap.keys()]) + renamed_regs = set([key.reg_name for key in cmap]) return self._rename_args_recursive(cmap, renamed_regs) @@ -381,7 +366,7 @@ def __str__(self) -> str: class And(BinaryOp): @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] & args[1] def eval_vals(self) -> ArgType: @@ -393,13 +378,13 @@ def eval_vals(self) -> ArgType: class Or(BinaryOp): @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] | args[1] class Xor(BinaryOp): @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] ^ args[1] @@ -433,7 +418,7 @@ def __init__(self, arg1: BitArgType) -> None: self.args = [arg1] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return 1 - args[0] @@ -443,7 +428,7 @@ def __init__(self) -> None: self.args = [] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return 0 @@ -453,7 +438,7 @@ def __init__(self) -> None: self.args = [] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return 1 @@ -538,13 +523,13 @@ class PredicateExp(BinaryOp): class Eq(PredicateExp): @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] == args[1] class Neq(PredicateExp): @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return 1 - Eq._const_eval(args) @@ -578,7 +563,7 @@ def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: self.args = [arg1, arg2] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] < args[1] @@ -588,7 +573,7 @@ def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: self.args = [arg1, arg2] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] > args[1] @@ -598,7 +583,7 @@ def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: self.args = [arg1, arg2] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] <= args[1] @@ -608,53 +593,53 @@ def __init__(self, arg1: RegArgType, arg2: RegArgType) -> None: self.args = [arg1, arg2] @staticmethod - def _const_eval(args: List[Constant]) -> Constant: + def _const_eval(args: list[Constant]) -> Constant: return args[0] >= args[1] -def reg_eq(register: Union[RegLogicExp, BitRegister], value: Constant) -> RegLogicExp: +def reg_eq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: """Function to express a BitRegister equality predicate, i.e. for a register ``r``, ``(r == 5)`` is expressed as ``reg_eq(r, 5)``""" return RegEq(register, value) -def reg_neq(register: Union[RegLogicExp, BitRegister], value: Constant) -> RegLogicExp: +def reg_neq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: """Function to express a BitRegister inequality predicate, i.e. for a register ``r``, ``(r != 5)`` is expressed as ``reg_neq(r, 5)``""" return RegNeq(register, value) -def reg_lt(register: Union[RegLogicExp, BitRegister], value: Constant) -> RegLogicExp: +def reg_lt(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: """Function to express a BitRegister less than predicate, i.e. for a register ``r``, ``(r < 5)`` is expressed as ``reg_lt(r, 5)``""" return RegLt(register, value) -def reg_gt(register: Union[RegLogicExp, BitRegister], value: Constant) -> RegLogicExp: +def reg_gt(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: """Function to express a BitRegister greater than predicate, i.e. for a register ``r``, ``(r > 5)`` is expressed as ``reg_gt(r, 5)``""" return RegGt(register, value) -def reg_leq(register: Union[RegLogicExp, BitRegister], value: Constant) -> RegLogicExp: +def reg_leq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: """Function to express a BitRegister less than or equal to predicate, i.e. for a register ``r``, ``(r <= 5)`` is expressed as ``reg_leq(r, 5)``""" return RegLeq(register, value) -def reg_geq(register: Union[RegLogicExp, BitRegister], value: Constant) -> RegLogicExp: +def reg_geq(register: RegLogicExp | BitRegister, value: Constant) -> RegLogicExp: """Function to express a BitRegister greater than or equal to predicate, i.e. for a register ``r``, ``(r >= 5)`` is expressed as ``reg_geq(r, 5)``""" return RegGeq(register, value) -def if_bit(bit: Union[Bit, BitLogicExp]) -> PredicateExp: +def if_bit(bit: Bit | BitLogicExp) -> PredicateExp: """Equivalent of ``if bit:``.""" return BitEq(bit, 1) -def if_not_bit(bit: Union[Bit, BitLogicExp]) -> PredicateExp: +def if_not_bit(bit: Bit | BitLogicExp) -> PredicateExp: """Equivalent of ``if not bit:``.""" return BitEq(bit, 0) @@ -681,7 +666,7 @@ def create_bit_logic_exp(op: BitWiseOp, args: Sequence[BitArgType]) -> BitLogicE if op == BitWiseOp.ZERO: assert len(args) == 0 return BitZero() - if op == BitWiseOp.ONE: + if op == BitWiseOp.ONE: # noqa: RET503 assert len(args) == 0 return BitOne() @@ -753,13 +738,12 @@ def create_logic_exp(op: Ops, args: Sequence[ArgType]) -> LogicExp: assert isinstance(arg, (BitLogicExp, Bit, Constant)) bit_args.append(arg) return create_bit_logic_exp(op, bit_args) - else: - assert isinstance(op, RegWiseOp) - reg_args = [] - for arg in args: - assert isinstance(arg, (RegLogicExp, BitRegister, Constant)) - reg_args.append(arg) - return create_reg_logic_exp(op, reg_args) + assert isinstance(op, RegWiseOp) + reg_args = [] + for arg in args: + assert isinstance(arg, (RegLogicExp, BitRegister, Constant)) + reg_args.append(arg) + return create_reg_logic_exp(op, reg_args) def create_predicate_exp(op: Ops, args: Sequence[ArgType]) -> PredicateExp: diff --git a/pytket/pytket/circuit/named_types.py b/pytket/pytket/circuit/named_types.py index 1673cb78a5..bf35d3362b 100644 --- a/pytket/pytket/circuit/named_types.py +++ b/pytket/pytket/circuit/named_types.py @@ -13,12 +13,13 @@ # limitations under the License. """Named types for convenience""" -from typing import Union, Sequence +from collections.abc import Sequence +from typing import Union from sympy import Expr from pytket._tket.circuit import Op -from pytket._tket.unit_id import UnitID, Qubit, Bit +from pytket._tket.unit_id import Bit, Qubit, UnitID ParamType = Union[float, Expr] """Type used for circuit parameters that can either diff --git a/pytket/pytket/circuit_library/__init__.py b/pytket/pytket/circuit_library/__init__.py index 1cd3275f1e..37441072bd 100644 --- a/pytket/pytket/circuit_library/__init__.py +++ b/pytket/pytket/circuit_library/__init__.py @@ -13,100 +13,100 @@ # limitations under the License. from pytket._tket.circuit_library import ( - BRIDGE_using_CX_0, - BRIDGE_using_CX_1, - CX_using_TK2, - TK2_using_CX, - TK2_using_CX_and_swap, - approx_TK2_using_1xCX, - approx_TK2_using_2xCX, - TK2_using_3xCX, - CX_using_flipped_CX, - CX_using_ECR, - CX_using_ZZMax, - CX_using_ZZPhase, - CX_using_XXPhase_0, - CX_using_XXPhase_1, - CX_using_AAMS, - CX_VS_CX_reduced, - CX_V_CX_reduced, - CX_S_CX_reduced, - CX_V_S_XC_reduced, - CX_S_V_XC_reduced, - CX_XC_reduced, - SWAP_using_CX_0, - SWAP_using_CX_1, + BRIDGE, + CCX, + CX, + H_CZ_H, X1_CX, Z0_CX, - CCX_modulo_phase_shift, - CCX_normal_decomp, + BRIDGE_using_CX_0, + BRIDGE_using_CX_1, C3X_normal_decomp, C4X_normal_decomp, - ladder_down, - ladder_down_2, - ladder_up, - X, - CX, - CCX, - BRIDGE, - H_CZ_H, - CZ_using_CX, - CY_using_CX, + CCX_modulo_phase_shift, + CCX_normal_decomp, CH_using_CX, - CV_using_CX, - CVdg_using_CX, - CSX_using_CX, - CSXdg_using_CX, + CRx_using_CX, + CRx_using_TK2, + CRy_using_CX, + CRy_using_TK2, + CRz_using_CX, + CRz_using_TK2, CS_using_CX, CSdg_using_CX, CSWAP_using_CX, - ECR_using_CX, - ZZMax_using_CX, - CRz_using_TK2, - CRz_using_CX, - CRx_using_TK2, - CRx_using_CX, - CRy_using_TK2, - CRy_using_CX, - CU1_using_TK2, + CSX_using_CX, + CSXdg_using_CX, CU1_using_CX, + CU1_using_TK2, CU3_using_CX, - ISWAP_using_TK2, - ISWAP_using_CX, - XXPhase_using_TK2, - XXPhase_using_CX, - YYPhase_using_TK2, - YYPhase_using_CX, - ZZPhase_using_TK2, - ZZPhase_using_CX, - TK2_using_ZZPhase, - TK2_using_ZZPhase_and_swap, - TK2_using_TK2_or_swap, - approx_TK2_using_1xZZPhase, - approx_TK2_using_2xZZPhase, - TK2_using_ZZMax, - TK2_using_ZZMax_and_swap, - XXPhase3_using_TK2, - XXPhase3_using_CX, - ESWAP_using_TK2, + CV_using_CX, + CVdg_using_CX, + CX_S_CX_reduced, + CX_S_V_XC_reduced, + CX_using_AAMS, + CX_using_ECR, + CX_using_flipped_CX, + CX_using_TK2, + CX_using_XXPhase_0, + CX_using_XXPhase_1, + CX_using_ZZMax, + CX_using_ZZPhase, + CX_V_CX_reduced, + CX_V_S_XC_reduced, + CX_VS_CX_reduced, + CX_XC_reduced, + CY_using_CX, + CZ_using_CX, + ECR_using_CX, ESWAP_using_CX, - FSim_using_TK2, + ESWAP_using_TK2, FSim_using_CX, - PhasedISWAP_using_TK2, - PhasedISWAP_using_CX, + FSim_using_TK2, + ISWAP_using_CX, + ISWAP_using_TK2, NPhasedX_using_PhasedX, - TK2_using_normalised_TK2, + PhasedISWAP_using_CX, + PhasedISWAP_using_TK2, + Rx_using_GPI, + Ry_using_GPI, + Rz_using_GPI, + SWAP_using_CX_0, + SWAP_using_CX_1, TK1_to_PhasedXRz, - TK1_to_RzRx, TK1_to_RzH, + TK1_to_RzRx, TK1_to_RzSX, TK1_to_TK1, - Rx_using_GPI, - Ry_using_GPI, - Rz_using_GPI, + TK1_using_GPI, + TK2_using_3xCX, + TK2_using_AAMS, + TK2_using_CX, + TK2_using_CX_and_swap, + TK2_using_normalised_TK2, + TK2_using_TK2_or_swap, + TK2_using_ZZMax, + TK2_using_ZZMax_and_swap, + TK2_using_ZZPhase, + TK2_using_ZZPhase_and_swap, + X, + XXPhase3_using_CX, + XXPhase3_using_TK2, XXPhase_using_AAMS, + XXPhase_using_CX, + XXPhase_using_TK2, YYPhase_using_AAMS, + YYPhase_using_CX, + YYPhase_using_TK2, + ZZMax_using_CX, ZZPhase_using_AAMS, - TK1_using_GPI, - TK2_using_AAMS, + ZZPhase_using_CX, + ZZPhase_using_TK2, + approx_TK2_using_1xCX, + approx_TK2_using_1xZZPhase, + approx_TK2_using_2xCX, + approx_TK2_using_2xZZPhase, + ladder_down, + ladder_down_2, + ladder_up, ) diff --git a/pytket/pytket/config/__init__.py b/pytket/pytket/config/__init__.py index 419c6b5ee1..6a6f365818 100644 --- a/pytket/pytket/config/__init__.py +++ b/pytket/pytket/config/__init__.py @@ -16,9 +16,9 @@ The configuration is saved to and loaded from file.""" from .pytket_config import ( - get_config_file_path, PytketConfig, PytketExtConfig, + get_config_file_path, load_config_file, write_config_file, ) diff --git a/pytket/pytket/config/pytket_config.py b/pytket/pytket/config/pytket_config.py index d5229239bf..3df189f551 100644 --- a/pytket/pytket/config/pytket_config.py +++ b/pytket/pytket/config/pytket_config.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from abc import ABC, abstractmethod -from pathlib import Path -from typing import Any, ClassVar, Dict, Optional, TypeVar, Type -from dataclasses import asdict, dataclass import json import os +from abc import ABC, abstractmethod +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Any, ClassVar, TypeVar def get_config_file_path() -> Path: @@ -29,20 +29,18 @@ def get_config_file_path() -> Path: else: config_dir = Path(xdg_conifg_dir) - pytket_config_file = config_dir / "pytket" / "config.json" - - return pytket_config_file + return config_dir / "pytket" / "config.json" class PytketConfig: """PytketConfig represents a loaded config file for pytket and extension packages.""" - extensions: Dict[str, Any] + extensions: dict[str, Any] def __init__( self, - extensions: Optional[Dict[str, Any]] = None, + extensions: dict[str, Any] | None = None, ) -> None: """Construct a PytketConfig object with inital config parameter values. @@ -98,23 +96,23 @@ class PytketExtConfig(ABC): @classmethod @abstractmethod - def from_extension_dict(cls: Type[T], ext_dict: Dict[str, Any]) -> T: + def from_extension_dict(cls: type[T], ext_dict: dict[str, Any]) -> T: """Abstract method to build PytketExtConfig from dictionary serialized form.""" ... - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Serialize to dictionary.""" return asdict(self) @classmethod - def from_pytketconfig(cls: Type[T], p_config: PytketConfig) -> T: + def from_pytketconfig(cls: type[T], p_config: PytketConfig) -> T: """Build from PytketConfig instance.""" if cls.ext_dict_key in p_config.extensions: return cls.from_extension_dict(p_config.extensions[cls.ext_dict_key]) return cls.from_extension_dict({}) @classmethod - def from_default_config_file(cls: Type[T]) -> T: + def from_default_config_file(cls: type[T]) -> T: """Load from default config file.""" return cls.from_pytketconfig(load_config_file()) diff --git a/pytket/pytket/passes/__init__.py b/pytket/pytket/passes/__init__.py index 9a0a77a02b..65d104fab1 100644 --- a/pytket/pytket/passes/__init__.py +++ b/pytket/pytket/passes/__init__.py @@ -16,7 +16,7 @@ from pytket._tket.passes import * -from .script import compilation_pass_from_script, compilation_pass_grammar from .auto_rebase import auto_rebase_pass, auto_squash_pass from .passselector import PassSelector from .resizeregpass import scratch_reg_resize_pass +from .script import compilation_pass_from_script, compilation_pass_grammar diff --git a/pytket/pytket/passes/auto_rebase.py b/pytket/pytket/passes/auto_rebase.py index 3b91fdedab..37e5d22d1c 100644 --- a/pytket/pytket/passes/auto_rebase.py +++ b/pytket/pytket/passes/auto_rebase.py @@ -13,7 +13,7 @@ # limitations under the License. import warnings -from typing import Set + from pytket.circuit import OpType from pytket.passes import AutoRebase, AutoSquash, BasePass @@ -34,7 +34,7 @@ def __init__(self, *args: object, **kwargs: dict) -> None: super().__init__(*args, **kwargs) -def auto_rebase_pass(gateset: Set[OpType], allow_swaps: bool = False) -> BasePass: +def auto_rebase_pass(gateset: set[OpType], allow_swaps: bool = False) -> BasePass: """Attempt to generate a rebase pass automatically for the given target gateset. @@ -44,8 +44,8 @@ def auto_rebase_pass(gateset: Set[OpType], allow_swaps: bool = False) -> BasePas Raises an error if no known decompositions can be found, in which case try using RebaseCustom with your own decompositions. - In addition to the gate types in ``gateset``, any ``Measure``, ``Reset`` and - ``Collapse`` operations in the original circuit are retained. Conditional + In addition to the gate types in ``gateset``, any ``Measure`` and ``Reset`` + operations in the original circuit are retained. Conditional operations are also allowed. ``Phase`` gates may also be introduced. :param gateset: Set of supported OpTypes, target gate set. @@ -70,7 +70,7 @@ def auto_rebase_pass(gateset: Set[OpType], allow_swaps: bool = False) -> BasePas ) -def auto_squash_pass(gateset: Set[OpType]) -> BasePass: +def auto_squash_pass(gateset: set[OpType]) -> BasePass: """Attempt to generate a squash pass automatically for the given target single qubit gateset. diff --git a/pytket/pytket/passes/passselector.py b/pytket/pytket/passes/passselector.py index 2e4ba5ec6f..26d3ab8b8d 100644 --- a/pytket/pytket/passes/passselector.py +++ b/pytket/pytket/passes/passselector.py @@ -12,9 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, Callable +from collections.abc import Callable from pytket.circuit import Circuit + from .._tket.passes import BasePass @@ -51,13 +52,14 @@ def apply(self, circ: Circuit) -> Circuit: """ circ_list = [circ.copy() for _ in self._passlist] - self._scores: list[Optional[int]] = [] + self._scores: list[int | None] = [] for p, c in zip(self._passlist, circ_list): try: p.apply(c) self._scores.append(self._score_func(c)) - except: # in case of any error the pass should be ignored + except: # noqa: E722 + # in case of any error the pass should be ignored self._scores.append(None) try: @@ -67,7 +69,7 @@ def apply(self, circ: Circuit) -> Circuit: except ValueError: raise RuntimeError("No passes have successfully run on this circuit") - def get_scores(self) -> list[Optional[int]]: + def get_scores(self) -> list[int | None]: """ :return: scores of the circuit after compiling for each of the compilations passes diff --git a/pytket/pytket/passes/resizeregpass.py b/pytket/pytket/passes/resizeregpass.py index 494b1c2b49..412d72f470 100644 --- a/pytket/pytket/passes/resizeregpass.py +++ b/pytket/pytket/passes/resizeregpass.py @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from pytket.circuit import Bit, Circuit from pytket.unit_id import _TEMP_BIT_NAME -from pytket.circuit import Circuit, Bit + from .._tket.passes import BasePass, CustomPass MAX_C_REG_WIDTH = 32 diff --git a/pytket/pytket/passes/script.py b/pytket/pytket/passes/script.py index aa467135d7..eb2b6a5c30 100644 --- a/pytket/pytket/passes/script.py +++ b/pytket/pytket/passes/script.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import List, cast +from typing import cast + from lark import Lark, Transformer -from pytket.circuit import OpType -from pytket.passes import BasePass, RepeatPass, SequencePass + +from pytket.circuit import CXConfigType, OpType from pytket.passes import ( + BasePass, CliffordSimp, CommuteThroughMultis, ContextSimp, @@ -32,21 +34,22 @@ GuidedPauliSimp, KAKDecomposition, OptimisePhaseGadgets, - PauliSimp, PauliExponentials, + PauliSimp, PauliSquash, PeepholeOptimise2Q, RebaseTket, RemoveBarriers, RemoveDiscarded, RemoveRedundancies, + RepeatPass, + SequencePass, SimplifyInitial, SimplifyMeasured, SynthesiseTket, SynthesiseUMD, ThreeQubitSquash, ) -from pytket.circuit import CXConfigType from pytket.transform import PauliSynthStrat pass_grammar = """ @@ -181,166 +184,166 @@ def pass_list(self, t: list[BasePass]) -> BasePass: def repeat_pass(self, t: list[BasePass]) -> BasePass: return RepeatPass(t[0]) - def clifford_simp(self, t: List) -> BasePass: + def clifford_simp(self, t: list) -> BasePass: return CliffordSimp() - def clifford_simp_no_swaps(self, t: List) -> BasePass: + def clifford_simp_no_swaps(self, t: list) -> BasePass: return CliffordSimp(allow_swaps=False) - def commute_through_multis(self, t: List) -> BasePass: + def commute_through_multis(self, t: list) -> BasePass: return CommuteThroughMultis() - def context_simp(self, t: List) -> BasePass: + def context_simp(self, t: list) -> BasePass: return ContextSimp() - def context_simp_no_classical(self, t: List) -> BasePass: + def context_simp_no_classical(self, t: list) -> BasePass: return ContextSimp(allow_classical=False) - def decompose_arbitrarily_controlled_gates(self, t: List) -> BasePass: + def decompose_arbitrarily_controlled_gates(self, t: list) -> BasePass: return DecomposeArbitrarilyControlledGates() - def decompose_boxes(self, t: List) -> BasePass: + def decompose_boxes(self, t: list) -> BasePass: return DecomposeBoxes() - def decompose_classical_exp(self, t: List) -> BasePass: + def decompose_classical_exp(self, t: list) -> BasePass: return DecomposeClassicalExp() - def decompose_multi_qubits_cx(self, t: List) -> BasePass: + def decompose_multi_qubits_cx(self, t: list) -> BasePass: return DecomposeMultiQubitsCX() - def decompose_single_qubits_tk1(self, t: List) -> BasePass: + def decompose_single_qubits_tk1(self, t: list) -> BasePass: return DecomposeSingleQubitsTK1() - def delay_measures(self, t: List) -> BasePass: + def delay_measures(self, t: list) -> BasePass: return DelayMeasures(False) - def delay_measures_try(self, t: List) -> BasePass: + def delay_measures_try(self, t: list) -> BasePass: return DelayMeasures(True) def euler_angle_reduction(self, t: list[OpType]) -> BasePass: return EulerAngleReduction(t[0], t[1]) - def flatten_registers(self, t: List) -> BasePass: + def flatten_registers(self, t: list) -> BasePass: return FlattenRegisters() - def full_peephole_optimise(self, t: List) -> BasePass: + def full_peephole_optimise(self, t: list) -> BasePass: return FullPeepholeOptimise() - def full_peephole_optimise_no_swaps(self, t: List) -> BasePass: + def full_peephole_optimise_no_swaps(self, t: list) -> BasePass: return FullPeepholeOptimise(allow_swaps=False) - def guided_pauli_simp(self, t: List) -> BasePass: + def guided_pauli_simp(self, t: list) -> BasePass: assert isinstance(t[0], PauliSynthStrat) assert isinstance(t[1], CXConfigType) return GuidedPauliSimp(strat=t[0], cx_config=t[1]) - def guided_pauli_simp_default(self, t: List) -> BasePass: + def guided_pauli_simp_default(self, t: list) -> BasePass: return GuidedPauliSimp() - def kak_decomposition(self, t: List) -> BasePass: + def kak_decomposition(self, t: list) -> BasePass: return KAKDecomposition() - def optimise_phase_gadgets(self, t: List) -> BasePass: + def optimise_phase_gadgets(self, t: list) -> BasePass: assert isinstance(t[0], CXConfigType) return OptimisePhaseGadgets(cx_config=t[0]) - def optimise_phase_gadgets_default(self, t: List) -> BasePass: + def optimise_phase_gadgets_default(self, t: list) -> BasePass: return OptimisePhaseGadgets() - def pauli_exponentials(self, t: List) -> BasePass: + def pauli_exponentials(self, t: list) -> BasePass: assert isinstance(t[0], PauliSynthStrat) assert isinstance(t[1], CXConfigType) return PauliExponentials(strat=t[0], cx_config=t[1]) - def pauli_exponentials_default(self, t: List) -> BasePass: + def pauli_exponentials_default(self, t: list) -> BasePass: return PauliExponentials() - def pauli_simp(self, t: List) -> BasePass: + def pauli_simp(self, t: list) -> BasePass: assert isinstance(t[0], PauliSynthStrat) assert isinstance(t[1], CXConfigType) return PauliSimp(strat=t[0], cx_config=t[1]) - def pauli_simp_default(self, t: List) -> BasePass: + def pauli_simp_default(self, t: list) -> BasePass: return PauliSimp() - def pauli_squash(self, t: List) -> BasePass: + def pauli_squash(self, t: list) -> BasePass: assert isinstance(t[0], PauliSynthStrat) assert isinstance(t[1], CXConfigType) return PauliSquash(strat=t[0], cx_config=t[1]) - def pauli_squash_default(self, t: List) -> BasePass: + def pauli_squash_default(self, t: list) -> BasePass: return PauliSquash() - def peephole_optimise_2q(self, t: List) -> BasePass: + def peephole_optimise_2q(self, t: list) -> BasePass: return PeepholeOptimise2Q() - def rebase_tket(self, t: List) -> BasePass: + def rebase_tket(self, t: list) -> BasePass: return RebaseTket() - def remove_barriers(self, t: List) -> BasePass: + def remove_barriers(self, t: list) -> BasePass: return RemoveBarriers() - def remove_discarded(self, t: List) -> BasePass: + def remove_discarded(self, t: list) -> BasePass: return RemoveDiscarded() - def remove_redundancies(self, t: List) -> BasePass: + def remove_redundancies(self, t: list) -> BasePass: return RemoveRedundancies() - def simplify_initial(self, t: List) -> BasePass: + def simplify_initial(self, t: list) -> BasePass: return SimplifyInitial() - def simplify_initial_no_classical(self, t: List) -> BasePass: + def simplify_initial_no_classical(self, t: list) -> BasePass: return SimplifyInitial(allow_classical=False) - def simplify_measured(self, t: List) -> BasePass: + def simplify_measured(self, t: list) -> BasePass: return SimplifyMeasured() - def synthesise_tket(self, t: List) -> BasePass: + def synthesise_tket(self, t: list) -> BasePass: return SynthesiseTket() - def synthesise_umd(self, t: List) -> BasePass: + def synthesise_umd(self, t: list) -> BasePass: return SynthesiseUMD() - def three_qubit_squash(self, t: List) -> BasePass: + def three_qubit_squash(self, t: list) -> BasePass: return ThreeQubitSquash() - def cx_config_type(self, t: List[CXConfigType]) -> CXConfigType: + def cx_config_type(self, t: list[CXConfigType]) -> CXConfigType: return t[0] - def cx_config_type_snake(self, t: List) -> CXConfigType: + def cx_config_type_snake(self, t: list) -> CXConfigType: return CXConfigType.Snake - def cx_config_type_star(self, t: List) -> CXConfigType: + def cx_config_type_star(self, t: list) -> CXConfigType: return CXConfigType.Star - def cx_config_type_tree(self, t: List) -> CXConfigType: + def cx_config_type_tree(self, t: list) -> CXConfigType: return CXConfigType.Tree - def cx_config_type_multi_q_gate(self, t: List) -> CXConfigType: + def cx_config_type_multi_q_gate(self, t: list) -> CXConfigType: return CXConfigType.MultiQGate - def op_type(self, t: List[OpType]) -> OpType: + def op_type(self, t: list[OpType]) -> OpType: return t[0] - def op_type_rx(self, t: List) -> OpType: + def op_type_rx(self, t: list) -> OpType: return OpType.Rx - def op_type_ry(self, t: List) -> OpType: + def op_type_ry(self, t: list) -> OpType: return OpType.Ry - def op_type_rz(self, t: List) -> OpType: + def op_type_rz(self, t: list) -> OpType: return OpType.Rz - def pauli_synth_strat(self, t: List[PauliSynthStrat]) -> PauliSynthStrat: + def pauli_synth_strat(self, t: list[PauliSynthStrat]) -> PauliSynthStrat: return t[0] - def pauli_synth_strat_individual(self, t: List) -> PauliSynthStrat: + def pauli_synth_strat_individual(self, t: list) -> PauliSynthStrat: return PauliSynthStrat.Individual - def pauli_synth_strat_pairwise(self, t: List) -> PauliSynthStrat: + def pauli_synth_strat_pairwise(self, t: list) -> PauliSynthStrat: return PauliSynthStrat.Pairwise - def pauli_synth_strat_sets(self, t: List) -> PauliSynthStrat: + def pauli_synth_strat_sets(self, t: list) -> PauliSynthStrat: return PauliSynthStrat.Sets diff --git a/pytket/pytket/qasm/__init__.py b/pytket/pytket/qasm/__init__.py index 4a4bbd14e1..19988d435a 100644 --- a/pytket/pytket/qasm/__init__.py +++ b/pytket/pytket/qasm/__init__.py @@ -18,10 +18,10 @@ from .qasm import ( circuit_from_qasm, - circuit_to_qasm, - circuit_from_qasm_str, - circuit_to_qasm_str, circuit_from_qasm_io, - circuit_to_qasm_io, + circuit_from_qasm_str, circuit_from_qasm_wasm, + circuit_to_qasm, + circuit_to_qasm_io, + circuit_to_qasm_str, ) diff --git a/pytket/pytket/qasm/grammar.py b/pytket/pytket/qasm/grammar.py index 50082ee3d7..493d7e4954 100644 --- a/pytket/pytket/qasm/grammar.py +++ b/pytket/pytket/qasm/grammar.py @@ -69,7 +69,7 @@ _pow_exp: ipow | _atom_exp ipow: _pow_exp "**" _atom_exp -_atom_exp: "(" _exp ")" +_atom_exp: "(" _exp ")" | cce_call | _atom diff --git a/pytket/pytket/qasm/qasm.py b/pytket/pytket/qasm/qasm.py index 86cba07ed1..605689dd1c 100644 --- a/pytket/pytket/qasm/qasm.py +++ b/pytket/pytket/qasm/qasm.py @@ -12,51 +12,34 @@ # See the License for the specific language governing permissions and # limitations under the License. -from dataclasses import dataclass +import itertools import os import re import uuid - -import itertools from collections import OrderedDict +from collections.abc import Callable, Generator, Iterable, Iterator, Sequence +from dataclasses import dataclass +from decimal import Decimal from importlib import import_module from itertools import chain, groupby -from decimal import Decimal -from typing import ( - Any, - Callable, - Dict, - Generator, - Iterable, - Iterator, - List, - NewType, - Optional, - Sequence, - Set, - TextIO, - Tuple, - Type, - TypeVar, - Union, - cast, -) -from sympy import Symbol, pi, Expr +from typing import Any, NewType, TextIO, TypeVar, Union, cast + from lark import Discard, Lark, Token, Transformer, Tree +from sympy import Expr, Symbol, pi from pytket._tket.circuit import ( + BarrierOp, ClassicalExpBox, + ClExpr, + ClExprOp, Command, Conditional, - RangePredicateOp, - SetBitsOp, CopyBitsOp, MultiBitOp, + RangePredicateOp, + SetBitsOp, WASMOp, - BarrierOp, - ClExprOp, WiredClExpr, - ClExpr, ) from pytket._tket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE from pytket.circuit import ( @@ -69,31 +52,38 @@ QubitRegister, UnitID, ) -from pytket.circuit.clexpr import has_reg_output, wired_clexpr_from_logic_exp +from pytket.circuit.clexpr import ( + check_register_alignments, + has_reg_output, + wired_clexpr_from_logic_exp, +) from pytket.circuit.decompose_classical import int_to_bools from pytket.circuit.logic_exp import ( BitLogicExp, BitWiseOp, - PredicateExp, LogicExp, + PredicateExp, RegEq, RegLogicExp, RegNeg, RegWiseOp, - create_predicate_exp, create_logic_exp, + create_predicate_exp, +) +from pytket.passes import ( + AutoRebase, + DecomposeBoxes, + RemoveRedundancies, + scratch_reg_resize_pass, ) from pytket.qasm.grammar import grammar -from pytket.passes import AutoRebase, DecomposeBoxes, RemoveRedundancies from pytket.wasm import WasmFileHandler class QASMParseError(Exception): """Error while parsing QASM input.""" - def __init__( - self, msg: str, line: Optional[int] = None, fname: Optional[str] = None - ): + def __init__(self, msg: str, line: int | None = None, fname: str | None = None): self.msg = msg self.line = line self.fname = fname @@ -115,7 +105,7 @@ class QASMUnsupportedError(Exception): _BITOPS.update(("+", "-")) # both are parsed to XOR _REGOPS = set(op.value for op in RegWiseOp) -Arg = Union[List, str] +Arg = Union[list, str] NOPARAM_COMMANDS = { @@ -193,16 +183,16 @@ class QASMUnsupportedError(Exception): "fsim": OpType.FSim, } -_tk_to_qasm_noparams = dict(((item[1], item[0]) for item in NOPARAM_COMMANDS.items())) +_tk_to_qasm_noparams = dict((item[1], item[0]) for item in NOPARAM_COMMANDS.items()) _tk_to_qasm_noparams[OpType.CX] = "cx" # prefer "cx" to "CX" -_tk_to_qasm_params = dict(((item[1], item[0]) for item in PARAM_COMMANDS.items())) +_tk_to_qasm_params = dict((item[1], item[0]) for item in PARAM_COMMANDS.items()) _tk_to_qasm_params[OpType.U3] = "u3" # prefer "u3" to "U" _tk_to_qasm_params[OpType.Rz] = "rz" # prefer "rz" to "Rz" _tk_to_qasm_extra_noparams = dict( - ((item[1], item[0]) for item in NOPARAM_EXTRA_COMMANDS.items()) + (item[1], item[0]) for item in NOPARAM_EXTRA_COMMANDS.items() ) _tk_to_qasm_extra_params = dict( - ((item[1], item[0]) for item in PARAM_EXTRA_COMMANDS.items()) + (item[1], item[0]) for item in PARAM_EXTRA_COMMANDS.items() ) _classical_gatestr_map = {"AND": "&", "OR": "|", "XOR": "^"} @@ -228,7 +218,7 @@ class QASMUnsupportedError(Exception): regname_regex = re.compile(r"^[a-z][a-zA-Z0-9_]*$") -def _extract_reg(var: Token) -> Tuple[str, int]: +def _extract_reg(var: Token) -> tuple[str, int]: match = unit_regex.match(var.value) if match is None: raise QASMParseError( @@ -243,10 +233,10 @@ def _extract_reg(var: Token) -> Tuple[str, int]: def _load_include_module( header_name: str, flter: bool, decls_only: bool -) -> Dict[str, Dict]: +) -> dict[str, dict]: try: if decls_only: - include_def: Dict[str, Dict] = import_module( + include_def: dict[str, dict] = import_module( f"pytket.qasm.includes._{header_name}_decls" )._INCLUDE_DECLS else: @@ -264,33 +254,33 @@ def _load_include_module( } -def _bin_par_exp(op: "str") -> Callable[["CircuitTransformer", List[str]], str]: - def f(self: "CircuitTransformer", vals: List[str]) -> str: +def _bin_par_exp(op: "str") -> Callable[["CircuitTransformer", list[str]], str]: + def f(self: "CircuitTransformer", vals: list[str]) -> str: return f"({vals[0]} {op} {vals[1]})" return f -def _un_par_exp(op: "str") -> Callable[["CircuitTransformer", List[str]], str]: - def f(self: "CircuitTransformer", vals: List[str]) -> str: +def _un_par_exp(op: "str") -> Callable[["CircuitTransformer", list[str]], str]: + def f(self: "CircuitTransformer", vals: list[str]) -> str: return f"({op}{vals[0]})" return f -def _un_call_exp(op: "str") -> Callable[["CircuitTransformer", List[str]], str]: - def f(self: "CircuitTransformer", vals: List[str]) -> str: +def _un_call_exp(op: "str") -> Callable[["CircuitTransformer", list[str]], str]: + def f(self: "CircuitTransformer", vals: list[str]) -> str: return f"{op}({vals[0]})" return f -def _hashable_uid(arg: List) -> Tuple[str, int]: +def _hashable_uid(arg: list) -> tuple[str, int]: return arg[0], arg[1][0] Reg = NewType("Reg", str) -CommandDict = Dict[str, Any] +CommandDict = dict[str, Any] @dataclass @@ -306,19 +296,19 @@ def __init__( self, return_gate_dict: bool = False, maxwidth: int = 32, - use_clexpr: bool = False, + use_clexpr: bool = True, ) -> None: super().__init__() - self.q_registers: Dict[str, int] = {} - self.c_registers: Dict[str, int] = {} - self.gate_dict: Dict[str, Dict] = {} - self.wasm: Optional[WasmFileHandler] = None + self.q_registers: dict[str, int] = {} + self.c_registers: dict[str, int] = {} + self.gate_dict: dict[str, dict] = {} + self.wasm: WasmFileHandler | None = None self.include = "" self.return_gate_dict = return_gate_dict self.maxwidth = maxwidth self.use_clexpr = use_clexpr - def _fresh_temp_bit(self) -> List: + def _fresh_temp_bit(self) -> list: if _TEMP_BIT_NAME in self.c_registers: idx = self.c_registers[_TEMP_BIT_NAME] else: @@ -338,17 +328,16 @@ def _reset_context(self, reset_wasm: bool = True) -> None: def _get_reg(self, name: str) -> Reg: return Reg(name) - def _get_uid(self, iarg: Token) -> List: + def _get_uid(self, iarg: Token) -> list: name, idx = _extract_reg(iarg) return [name, [idx]] def _get_arg(self, arg: Token) -> Arg: if arg.type == "IARG": return self._get_uid(arg) - else: - return self._get_reg(arg.value) + return self._get_reg(arg.value) - def unroll_all_args(self, args: Iterable[Arg]) -> Iterator[List[Any]]: + def unroll_all_args(self, args: Iterable[Arg]) -> Iterator[list[Any]]: for arg in args: if isinstance(arg, str): size = ( @@ -363,13 +352,13 @@ def unroll_all_args(self, args: Iterable[Arg]) -> Iterator[List[Any]]: def margs(self, tree: Iterable[Token]) -> Iterator[Arg]: return map(self._get_arg, tree) - def iargs(self, tree: Iterable[Token]) -> Iterator[List]: + def iargs(self, tree: Iterable[Token]) -> Iterator[list]: return map(self._get_uid, tree) - def args(self, tree: Iterable[Token]) -> Iterator[List]: + def args(self, tree: Iterable[Token]) -> Iterator[list]: return ([tok.value, [0]] for tok in tree) - def creg(self, tree: List[Token]) -> None: + def creg(self, tree: list[Token]) -> None: name, size = _extract_reg(tree[0]) if size > self.maxwidth: raise QASMUnsupportedError( @@ -379,17 +368,17 @@ def creg(self, tree: List[Token]) -> None: ) self.c_registers[Reg(name)] = size - def qreg(self, tree: List[Token]) -> None: + def qreg(self, tree: list[Token]) -> None: name, size = _extract_reg(tree[0]) self.q_registers[Reg(name)] = size - def meas(self, tree: List[Token]) -> Iterable[CommandDict]: + def meas(self, tree: list[Token]) -> Iterable[CommandDict]: for args in zip(*self.unroll_all_args(self.margs(tree))): yield {"args": list(args), "op": {"type": "Measure"}} - def barr(self, tree: List[Arg]) -> Iterable[CommandDict]: + def barr(self, tree: list[Arg]) -> Iterable[CommandDict]: args = [q for qs in self.unroll_all_args(tree[0]) for q in qs] - signature: List[str] = [] + signature: list[str] = [] for arg in args: if arg[0] in self.c_registers: signature.append("C") @@ -404,14 +393,14 @@ def barr(self, tree: List[Arg]) -> Iterable[CommandDict]: "op": {"signature": signature, "type": "Barrier"}, } - def reset(self, tree: List[Token]) -> Iterable[CommandDict]: + def reset(self, tree: list[Token]) -> Iterable[CommandDict]: for qb in next(self.unroll_all_args(self.margs(tree))): yield {"args": [qb], "op": {"type": "Reset"}} def pars(self, vals: Iterable[str]) -> ParsMap: return ParsMap(map(str, vals)) - def mixedcall(self, tree: List) -> Iterator[CommandDict]: + def mixedcall(self, tree: list) -> Iterator[CommandDict]: child_iter = iter(tree) optoken = next(child_iter) @@ -475,7 +464,7 @@ def mixedcall(self, tree: List) -> Iterator[CommandDict]: else: params = [f"({par})/pi" for par in pars] if opstr in self.gate_dict: - op: Dict[str, Any] = {} + op: dict[str, Any] = {} if opstr in treat_as_barrier: op["type"] = "Barrier" param_sorted = ",".join(params) @@ -499,7 +488,7 @@ def mixedcall(self, tree: List) -> Iterator[CommandDict]: optype = _all_string_maps[opstr] except KeyError as e: raise QASMParseError( - "Cannot parse gate of type: {}".format(opstr), optoken.line + f"Cannot parse gate of type: {opstr}", optoken.line ) from e op = {"type": optype} if params: @@ -514,7 +503,7 @@ def mixedcall(self, tree: List) -> Iterator[CommandDict]: for arg in zip(*self.unroll_all_args(args)): yield {"args": list(arg), "op": op} - def gatecall(self, tree: List) -> Iterable[CommandDict]: + def gatecall(self, tree: list) -> Iterable[CommandDict]: return self.mixedcall(tree) def exp_args(self, tree: Iterable[Token]) -> Iterable[Reg]: @@ -526,14 +515,16 @@ def exp_args(self, tree: Iterable[Token]) -> Iterable[Reg]: "Non register arguments not supported for extern call.", arg.line ) - def _logic_exp(self, tree: List, opstr: str) -> LogicExp: + def _logic_exp(self, tree: list, opstr: str) -> LogicExp: args, line = self._get_logic_args(tree) - openum: Union[Type[BitWiseOp], Type[RegWiseOp]] + openum: type[BitWiseOp] | type[RegWiseOp] if opstr in _BITOPS and opstr not in _REGOPS: openum = BitWiseOp - elif opstr in _REGOPS and opstr not in _BITOPS: - openum = RegWiseOp - elif all(isinstance(arg, int) for arg in args): + elif ( + opstr in _REGOPS + and opstr not in _BITOPS + or all(isinstance(arg, int) for arg in args) + ): openum = RegWiseOp elif all(isinstance(arg, (Bit, BitLogicExp, int)) for arg in args): if all(arg in (0, 1) for arg in args if isinstance(arg, int)): @@ -547,15 +538,15 @@ def _logic_exp(self, tree: List, opstr: str) -> LogicExp: else: openum = RegWiseOp if openum is BitWiseOp and opstr in ("+", "-"): - op: Union[BitWiseOp, RegWiseOp] = BitWiseOp.XOR + op: BitWiseOp | RegWiseOp = BitWiseOp.XOR else: op = openum(opstr) return create_logic_exp(op, args) def _get_logic_args( - self, tree: Sequence[Union[Token, LogicExp]] - ) -> Tuple[List[Union[LogicExp, Bit, BitRegister, int]], Optional[int]]: - args: List[Union[LogicExp, Bit, BitRegister, int]] = [] + self, tree: Sequence[Token | LogicExp] + ) -> tuple[list[LogicExp | Bit | BitRegister | int], int | None]: + args: list[LogicExp | Bit | BitRegister | int] = [] line = None for tok in tree: if isinstance(tok, LogicExp): @@ -600,14 +591,14 @@ def _get_logic_args( div = lambda self, tree: self._logic_exp(tree, "/") ipow = lambda self, tree: self._logic_exp(tree, "**") - def neg(self, tree: List[Union[Token, LogicExp]]) -> RegNeg: + def neg(self, tree: list[Token | LogicExp]) -> RegNeg: arg = self._get_logic_args(tree)[0][0] assert isinstance(arg, (RegLogicExp, BitRegister, int)) return RegNeg(arg) - def cond(self, tree: List[Token]) -> PredicateExp: - op: Union[BitWiseOp, RegWiseOp] - arg: Union[Bit, BitRegister] + def cond(self, tree: list[Token]) -> PredicateExp: + op: BitWiseOp | RegWiseOp + arg: Bit | BitRegister if tree[1].type == "IARG": arg = Bit(*_extract_reg(tree[1])) op = BitWiseOp(str(tree[2])) @@ -679,9 +670,9 @@ def cop(self, tree: Sequence[Iterable[CommandDict]]) -> Iterable[CommandDict]: return tree[0] def _calc_exp_io( - self, exp: LogicExp, out_args: List - ) -> Tuple[List[List], Dict[str, Any]]: - all_inps: list[Tuple[str, int]] = [] + self, exp: LogicExp, out_args: list + ) -> tuple[list[list], dict[str, Any]]: + all_inps: list[tuple[str, int]] = [] for inp in exp.all_inputs_ordered(): if isinstance(inp, Bit): all_inps.append((inp.reg_name, inp.index[0])) @@ -709,7 +700,7 @@ def _calc_exp_io( } return exp_args, numbers_dict - def _cexpbox_dict(self, exp: LogicExp, args: List[List]) -> CommandDict: + def _cexpbox_dict(self, exp: LogicExp, args: list[list]) -> CommandDict: box = { "exp": exp.to_dict(), "id": str(uuid.uuid4()), @@ -725,7 +716,7 @@ def _cexpbox_dict(self, exp: LogicExp, args: List[List]) -> CommandDict: }, } - def _clexpr_dict(self, exp: LogicExp, out_args: List[List]) -> CommandDict: + def _clexpr_dict(self, exp: LogicExp, out_args: list[list]) -> CommandDict: # Convert the LogicExp to a serialization of a command containing the # corresponding ClExprOp. wexpr, args = wired_clexpr_from_logic_exp( @@ -740,7 +731,7 @@ def _clexpr_dict(self, exp: LogicExp, out_args: List[List]) -> CommandDict: } def _logic_exp_as_cmd_dict( - self, exp: LogicExp, out_args: List[List] + self, exp: LogicExp, out_args: list[list] ) -> CommandDict: return ( self._clexpr_dict(exp, out_args) @@ -748,14 +739,14 @@ def _logic_exp_as_cmd_dict( else self._cexpbox_dict(exp, out_args) ) - def assign(self, tree: List) -> Iterable[CommandDict]: + def assign(self, tree: list) -> Iterable[CommandDict]: child_iter = iter(tree) out_args = list(next(child_iter)) args_uids = list(self.unroll_all_args(out_args)) exp_tree = next(child_iter) - exp: Union[str, List, LogicExp, int] = "" + exp: str | list | LogicExp | int = "" line = None if isinstance(exp_tree, Token): if exp_tree.type == "INT": @@ -783,7 +774,7 @@ def assign(self, tree: List) -> Iterable[CommandDict]: assert len(out_args) == 1 out_arg = out_args[0] args = args_uids[0] - if isinstance(out_arg, List): + if isinstance(out_arg, list): if isinstance(exp, LogicExp): yield self._logic_exp_as_cmd_dict(exp, args) elif isinstance(exp, (int, bool)): @@ -792,7 +783,7 @@ def assign(self, tree: List) -> Iterable[CommandDict]: "args": args, "op": {"classical": {"values": [bool(exp)]}, "type": "SetBits"}, } - elif isinstance(exp, List): + elif isinstance(exp, list): yield { "args": [exp] + args, "op": {"classical": {"n_i": 1}, "type": "CopyBits"}, @@ -826,14 +817,14 @@ def assign(self, tree: List) -> Iterable[CommandDict]: else: raise QASMParseError(f"Unexpected expression in assignment {exp}", line) - def extern(self, tree: List[Any]) -> Any: + def extern(self, tree: list[Any]) -> Any: # TODO parse extern defs return Discard - def ccall(self, tree: List) -> Iterable[CommandDict]: + def ccall(self, tree: list) -> Iterable[CommandDict]: return self.cce_call(tree) - def cce_call(self, tree: List) -> Iterable[CommandDict]: + def cce_call(self, tree: list) -> Iterable[CommandDict]: nam = tree[0].value params = list(tree[1]) if self.wasm is None: @@ -862,11 +853,11 @@ def cce_call(self, tree: List) -> Iterable[CommandDict]: }, } - def transform(self, tree: Tree) -> Dict[str, Any]: + def transform(self, tree: Tree) -> dict[str, Any]: self._reset_context() - return cast(Dict[str, Any], super().transform(tree)) + return cast(dict[str, Any], super().transform(tree)) - def gdef(self, tree: List) -> None: + def gdef(self, tree: list) -> None: child_iter = iter(tree) gate = next(child_iter).value @@ -920,7 +911,7 @@ def gdef(self, tree: List) -> None: ) if not existing_op: gate_circ.symbol_substitution(symbol_map) - gate_circ.rename_units(cast(Dict[UnitID, UnitID], rename_map)) + gate_circ.rename_units(cast(dict[UnitID, UnitID], rename_map)) self.gate_dict[gate] = { "definition": gate_circ.to_dict(), @@ -930,15 +921,15 @@ def gdef(self, tree: List) -> None: opaq = gdef - def oqasm(self, tree: List) -> Any: + def oqasm(self, tree: list) -> Any: return Discard - def incl(self, tree: List[Token]) -> None: + def incl(self, tree: list[Token]) -> None: self.include = str(tree[0].value).split(".")[0] self.gate_dict.update(_load_include_module(self.include, True, False)) - def prog(self, tree: Iterable) -> Dict[str, Any]: - outdict: Dict[str, Any] = { + def prog(self, tree: Iterable) -> dict[str, Any]: + outdict: dict[str, Any] = { "commands": list( chain.from_iterable( filter(lambda x: x is not None and x is not Discard, tree) @@ -987,7 +978,7 @@ def circuit_from_qasm( input_file: Union[str, "os.PathLike[Any]"], encoding: str = "utf-8", maxwidth: int = 32, - use_clexpr: bool = False, + use_clexpr: bool = True, ) -> Circuit: """A method to generate a tket Circuit from a qasm file. @@ -1000,7 +991,7 @@ def circuit_from_qasm( ext = os.path.splitext(input_file)[-1] if ext != ".qasm": raise TypeError("Can only convert .qasm files") - with open(input_file, "r", encoding=encoding) as f: + with open(input_file, encoding=encoding) as f: try: circ = circuit_from_qasm_io(f, maxwidth=maxwidth, use_clexpr=use_clexpr) except QASMParseError as e: @@ -1009,7 +1000,7 @@ def circuit_from_qasm( def circuit_from_qasm_str( - qasm_str: str, maxwidth: int = 32, use_clexpr: bool = False + qasm_str: str, maxwidth: int = 32, use_clexpr: bool = True ) -> Circuit: """A method to generate a tket Circuit from a qasm string. @@ -1024,11 +1015,15 @@ def circuit_from_qasm_str( cast(CircuitTransformer, g_parser.options.transformer)._reset_context( reset_wasm=False ) - return Circuit.from_dict(g_parser.parse(qasm_str)) # type: ignore[arg-type] + + circ = Circuit.from_dict(g_parser.parse(qasm_str)) # type: ignore[arg-type] + cpass = scratch_reg_resize_pass(maxwidth) + cpass.apply(circ) + return circ def circuit_from_qasm_io( - stream_in: TextIO, maxwidth: int = 32, use_clexpr: bool = False + stream_in: TextIO, maxwidth: int = 32, use_clexpr: bool = True ) -> Circuit: """A method to generate a tket Circuit from a qasm text stream""" return circuit_from_qasm_str( @@ -1041,7 +1036,7 @@ def circuit_from_qasm_wasm( wasm_file: Union[str, "os.PathLike[Any]"], encoding: str = "utf-8", maxwidth: int = 32, - use_clexpr: bool = False, + use_clexpr: bool = True, ) -> Circuit: """A method to generate a tket Circuit from a qasm string and external WASM module. @@ -1083,8 +1078,8 @@ def _filtered_qasm_str(qasm: str) -> str: # remove any c registers starting with _TEMP_BIT_NAME # that are not being used somewhere else lines = qasm.split("\n") - def_matcher = re.compile(r"creg ({}\_*\d*)\[\d+\]".format(_TEMP_BIT_NAME)) - arg_matcher = re.compile(r"({}\_*\d*)\[\d+\]".format(_TEMP_BIT_NAME)) + def_matcher = re.compile(rf"creg ({_TEMP_BIT_NAME}\_*\d*)\[\d+\]") + arg_matcher = re.compile(rf"({_TEMP_BIT_NAME}\_*\d*)\[\d+\]") unused_regs = dict() for i, line in enumerate(lines): if reg := def_matcher.match(line): @@ -1129,19 +1124,28 @@ def check_can_convert_circuit(circ: Circuit, header: str, maxwidth: int) -> None f"Circuit contains a classical register larger than {maxwidth}: try " "setting the `maxwidth` parameter to a higher value." ) - for cmd in circ: - if is_empty_customgate(cmd.op) or ( - isinstance(cmd.op, Conditional) and is_empty_customgate(cmd.op.op) - ): + set_circ_register = set([creg.name for creg in circ.c_registers]) + for b in circ.bits: + if b.reg_name not in set_circ_register: raise QASMUnsupportedError( - f"Empty CustomGates and opaque gates are not supported." + f"Circuit contains an invalid classical register {b.reg_name}." ) + # Empty CustomGates should have been removed by DecomposeBoxes(). + for cmd in circ: + assert not is_empty_customgate(cmd.op) + if isinstance(cmd.op, Conditional): + assert not is_empty_customgate(cmd.op.op) + if not check_register_alignments(circ): + raise QASMUnsupportedError( + "Circuit contains classical expressions on registers whose arguments or " + "outputs are not register-aligned." + ) def circuit_to_qasm_str( circ: Circuit, header: str = "qelib1", - include_gate_defs: Optional[Set[str]] = None, + include_gate_defs: set[str] | None = None, maxwidth: int = 32, ) -> str: """Convert a Circuit to QASM and return the string. @@ -1158,12 +1162,12 @@ def circuit_to_qasm_str( :return: qasm string """ - check_can_convert_circuit(circ, header, maxwidth) qasm_writer = QasmWriter( circ.qubits, circ.bits, header, include_gate_defs, maxwidth ) circ1 = circ.copy() DecomposeBoxes().apply(circ1) + check_can_convert_circuit(circ1, header, maxwidth) for command in circ1: assert isinstance(command, Command) qasm_writer.add_op(command.op, command.args) @@ -1174,8 +1178,8 @@ def circuit_to_qasm_str( def _retrieve_registers( - units: List[UnitID], reg_type: Type[TypeReg] -) -> Dict[str, TypeReg]: + units: list[UnitID], reg_type: type[TypeReg] +) -> dict[str, TypeReg]: if any(len(unit.index) != 1 for unit in units): raise NotImplementedError("OPENQASM registers must use a single index") maxunits = map(lambda x: max(x[1]), groupby(units, key=lambda un: un.reg_name)) @@ -1185,7 +1189,7 @@ def _retrieve_registers( } -def _parse_range(minval: int, maxval: int, maxwidth: int) -> Tuple[str, int]: +def _parse_range(minval: int, maxval: int, maxwidth: int) -> tuple[str, int]: if maxwidth > 64: raise NotImplementedError("Register width exceeds maximum of 64.") @@ -1193,38 +1197,35 @@ def _parse_range(minval: int, maxval: int, maxwidth: int) -> Tuple[str, int]: if minval > REGMAX: raise NotImplementedError("Range's lower bound exceeds register capacity.") - elif minval > maxval: + if minval > maxval: raise NotImplementedError("Range's lower bound exceeds upper bound.") - elif maxval > REGMAX: - maxval = REGMAX + maxval = min(maxval, REGMAX) if minval == maxval: return ("==", minval) - elif minval == 0: + if minval == 0: return ("<=", maxval) - elif maxval == REGMAX: + if maxval == REGMAX: return (">=", minval) - else: - raise NotImplementedError("Range can only be bounded on one side.") + raise NotImplementedError("Range can only be bounded on one side.") def _negate_comparator(comparator: str) -> str: if comparator == "==": return "!=" - elif comparator == "!=": + if comparator == "!=": return "==" - elif comparator == "<=": + if comparator == "<=": return ">" - elif comparator == ">": + if comparator == ">": return "<=" - elif comparator == ">=": + if comparator == ">=": return "<" - else: - assert comparator == "<" - return ">=" + assert comparator == "<" + return ">=" -def _get_optype_and_params(op: Op) -> Tuple[OpType, Optional[List[Union[float, Expr]]]]: +def _get_optype_and_params(op: Op) -> tuple[OpType, list[float | Expr] | None]: optype = op.type params = ( op.params @@ -1241,10 +1242,10 @@ def _get_optype_and_params(op: Op) -> Tuple[OpType, Optional[List[Union[float, E def _get_gate_circuit( - optype: OpType, qubits: List[Qubit], symbols: Optional[List[Symbol]] = None + optype: OpType, qubits: list[Qubit], symbols: list[Symbol] | None = None ) -> Circuit: # create Circuit for constructing qasm from - unitids = cast(List[UnitID], qubits) + unitids = cast(list[UnitID], qubits) gate_circ = Circuit() for q in qubits: gate_circ.add_qubit(q) @@ -1282,7 +1283,7 @@ class LabelledStringList: def __init__(self) -> None: self.strings: OrderedDict[int, str] = OrderedDict() - self.conditions: Dict[int, ConditionString] = dict() + self.conditions: dict[int, ConditionString] = dict() self.label = 0 def add_string(self, string: str) -> int: @@ -1291,7 +1292,7 @@ def add_string(self, string: str) -> int: self.label += 1 return label - def get_string(self, label: int) -> Optional[str]: + def get_string(self, label: int) -> str | None: return self.strings.get(label, None) def del_string(self, label: int) -> None: @@ -1311,7 +1312,7 @@ def get_full_string(self) -> str: return "".join(strings) -def make_params_str(params: Optional[List[Union[float, Expr]]]) -> str: +def make_params_str(params: list[float | Expr] | None) -> str: s = "" if params is not None: n_params = len(params) @@ -1319,20 +1320,19 @@ def make_params_str(params: Optional[List[Union[float, Expr]]]) -> str: for i in range(n_params): reduced = True try: - p: Union[float, Expr] = float(params[i]) + p: float | Expr = float(params[i]) except TypeError: reduced = False p = params[i] if i < n_params - 1: if reduced: - s += "{}*pi,".format(p) + s += f"{p}*pi," else: - s += "({})*pi,".format(p) + s += f"({p})*pi," + elif reduced: + s += f"{p}*pi)" else: - if reduced: - s += "{}*pi)".format(p) - else: - s += "({})*pi)".format(p) + s += f"({p})*pi)" s += " " return s @@ -1374,14 +1374,13 @@ def _var_appears(v: str, s: str) -> bool: # check if v appears in s and is not surrounded by word characters # e.g. a = a & b or a = a[1] & b[1] return bool(re.search(r"(? Bit: self.scratch_reg = BitRegister(self.scratch_reg.name, self.scratch_reg.size + 1) @@ -1468,7 +1467,7 @@ def remove_last_scratch_bit(self) -> None: assert self.scratch_reg.size > 0 self.scratch_reg = BitRegister(self.scratch_reg.name, self.scratch_reg.size - 1) - def write_params(self, params: Optional[List[Union[float, Expr]]]) -> None: + def write_params(self, params: list[float | Expr] | None) -> None: params_str = make_params_str(params) self.strings.add_string(params_str) @@ -1481,10 +1480,10 @@ def make_gate_definition( n_qubits: int, opstr: str, optype: OpType, - n_params: Optional[int] = None, + n_params: int | None = None, ) -> str: s = "gate " + opstr + " " - symbols: Optional[List[Symbol]] = None + symbols: list[Symbol] | None = None if n_params is not None: # need to add parameters to gate definition s += "(" @@ -1518,31 +1517,33 @@ def mark_as_written(self, label: int, written_variable: str) -> None: else: self.variable_writes[label] = [written_variable] - def add_range_predicate(self, op: RangePredicateOp, args: List[Bit]) -> None: - comparator, value = _parse_range(op.lower, op.upper, self.maxwidth) - if (not hqs_header(self.header)) and comparator != "==": + def check_range_predicate(self, op: RangePredicateOp, args: list[Bit]) -> None: + if (not hqs_header(self.header)) and op.lower != op.upper: raise QASMUnsupportedError( "OpenQASM conditions must be on a register's fixed value." ) - bits = args[:-1] + variable = args[0].reg_name + assert isinstance(variable, str) + if op.n_inputs != self.cregs[variable].size: + raise QASMUnsupportedError( + "RangePredicate conditions must be an entire classical register" + ) + if args[:-1] != self.cregs[variable].to_list(): + raise QASMUnsupportedError( + "RangePredicate conditions must be a single classical register" + ) + + def add_range_predicate(self, op: RangePredicateOp, args: list[Bit]) -> None: + self.check_range_predicate(op, args) + comparator, value = _parse_range(op.lower, op.upper, self.maxwidth) variable = args[0].reg_name dest_bit = str(args[-1]) - if not hqs_header(self.header): - assert isinstance(variable, str) - if op.n_inputs != self.cregs[variable].size: - raise QASMUnsupportedError( - "OpenQASM conditions must be an entire classical register" - ) - if bits != self.cregs[variable].to_list(): - raise QASMUnsupportedError( - "OpenQASM conditions must be a single classical register" - ) label = self.strings.add_string( "".join( [ f"if({variable}{comparator}{value}) " + f"{dest_bit} = 1;\n", f"if({variable}{_negate_comparator(comparator)}{value}) " - + f"{dest_bit} = 0;\n", + f"{dest_bit} = 0;\n", ] ) ) @@ -1576,9 +1577,9 @@ def replace_condition(self, pred_label: int) -> bool: line_labels.append(label) if "\n" not in string: continue - written_variables: List[str] = [] + written_variables: list[str] = [] # (label, condition) - conditions: List[Tuple[int, ConditionString]] = [] + conditions: list[tuple[int, ConditionString]] = [] for l in line_labels: written_variables.extend(self.variable_writes.get(l, [])) cond = self.strings.conditions.get(l) @@ -1653,9 +1654,42 @@ def add_conditional(self, op: Conditional, args: Sequence[UnitID]) -> None: # Conditional phase is ignored. return if op.op.type == OpType.RangePredicate: - raise QASMUnsupportedError( - "Conditional RangePredicate is currently unsupported." + # Special handling for nested ifs + # if condition + # if pred dest = 1 + # if not pred dest = 0 + # can be written as + # if condition s0 = 1 + # if pred s1 = 1 + # s2 = s0 & s1 + # s3 = s0 & ~s1 + # if s2 dest = 1 + # if s3 dest = 0 + # where s0, s1, s2, and s3 are scratch bits + s0 = self.fresh_scratch_bit() + l = self.strings.add_string(f"{s0} = 1;\n") + # we store the condition in self.strings.conditions + # as it can be later replaced by `replace_condition` + # if possible + self.strings.conditions[l] = ConditionString(variable, "==", op.value) + # output the RangePredicate to s1 + s1 = self.fresh_scratch_bit() + assert isinstance(op.op, RangePredicateOp) + self.check_range_predicate(op.op, cast(list[Bit], args[op.width :])) + pred_comparator, pred_value = _parse_range( + op.op.lower, op.op.upper, self.maxwidth ) + pred_variable = args[op.width :][0].reg_name + self.strings.add_string( + f"if({pred_variable}{pred_comparator}{pred_value}) {s1} = 1;\n" + ) + s2 = self.fresh_scratch_bit() + self.strings.add_string(f"{s2} = {s0} & {s1};\n") + s3 = self.fresh_scratch_bit() + self.strings.add_string(f"{s3} = {s0} & (~ {s1});\n") + self.strings.add_string(f"if({s2}==1) {args[-1]} = 1;\n") + self.strings.add_string(f"if({s3}==1) {args[-1]} = 0;\n") + return # we assign the condition to a scratch bit, which we will later remove # if the condition variable is unchanged. scratch_bit = self.fresh_scratch_bit() @@ -1684,7 +1718,7 @@ def add_conditional(self, op: Conditional, args: Sequence[UnitID]) -> None: # remove the unused scratch bit self.remove_last_scratch_bit() - def add_set_bits(self, op: SetBitsOp, args: List[Bit]) -> None: + def add_set_bits(self, op: SetBitsOp, args: list[Bit]) -> None: creg_name = args[0].reg_name bits, vals = zip(*sorted(zip(args, op.values))) # check if whole register can be set at once @@ -1697,7 +1731,7 @@ def add_set_bits(self, op: SetBitsOp, args: List[Bit]) -> None: label = self.strings.add_string(f"{bit} = {int(value)};\n") self.mark_as_written(label, f"{bit}") - def add_copy_bits(self, op: CopyBitsOp, args: List[Bit]) -> None: + def add_copy_bits(self, op: CopyBitsOp, args: list[Bit]) -> None: l_args = args[op.n_inputs :] r_args = args[: op.n_inputs] l_name = l_args[0].reg_name @@ -1714,7 +1748,7 @@ def add_copy_bits(self, op: CopyBitsOp, args: List[Bit]) -> None: label = self.strings.add_string(f"{bit_l} = {bit_r};\n") self.mark_as_written(label, f"{bit_l}") - def add_multi_bit(self, op: MultiBitOp, args: List[Bit]) -> None: + def add_multi_bit(self, op: MultiBitOp, args: list[Bit]) -> None: basic_op = op.basic_op basic_n = basic_op.n_inputs + basic_op.n_outputs + basic_op.n_input_outputs n_args = len(args) @@ -1734,7 +1768,7 @@ def add_multi_bit(self, op: MultiBitOp, args: List[Bit]) -> None: basic_args = args[basic_n * i : basic_n * (i + 1)] self.add_op(basic_op, basic_args) - def add_explicit_op(self, op: Op, args: List[Bit]) -> None: + def add_explicit_op(self, op: Op, args: list[Bit]) -> None: # &, ^ and | gates opstr = str(op) if opstr not in _classical_gatestr_map: @@ -1744,10 +1778,10 @@ def add_explicit_op(self, op: Op, args: List[Bit]) -> None: ) self.mark_as_written(label, f"{args[-1]}") - def add_classical_exp_box(self, op: ClassicalExpBox, args: List[Bit]) -> None: + def add_classical_exp_box(self, op: ClassicalExpBox, args: list[Bit]) -> None: out_args = args[op.get_n_i() :] if len(out_args) == 1: - label = self.strings.add_string(f"{out_args[0]} = {str(op.get_exp())};\n") + label = self.strings.add_string(f"{out_args[0]} = {op.get_exp()!s};\n") self.mark_as_written(label, f"{out_args[0]}") elif ( out_args @@ -1756,16 +1790,16 @@ def add_classical_exp_box(self, op: ClassicalExpBox, args: List[Bit]) -> None: ] ): label = self.strings.add_string( - f"{out_args[0].reg_name} = {str(op.get_exp())};\n" + f"{out_args[0].reg_name} = {op.get_exp()!s};\n" ) self.mark_as_written(label, f"{out_args[0].reg_name}") else: raise QASMUnsupportedError( - f"ClassicalExpBox only supported" + "ClassicalExpBox only supported" " for writing to a single bit or whole registers." ) - def add_wired_clexpr(self, op: ClExprOp, args: List[Bit]) -> None: + def add_wired_clexpr(self, op: ClExprOp, args: list[Bit]) -> None: wexpr: WiredClExpr = op.expr # 1. Determine the mappings from bit variables to bits and from register # variables to registers. @@ -1783,18 +1817,18 @@ def add_wired_clexpr(self, op: ClExprOp, args: List[Bit]) -> None: input_regs[i] = creg break else: - raise QASMUnsupportedError( - f"ClExprOp ({wexpr}) contains a register variable (r{i}) " - "that is not wired to any BitRegister in the circuit." + assert ( + not f"ClExprOp ({wexpr}) contains a register variable (r{i}) that " + "is not wired to any BitRegister in the circuit." ) # 2. Write the left-hand side of the assignment. - output_repr: Optional[str] = None + output_repr: str | None = None output_args: list[Bit] = [args[j] for j in output_posn] n_output_args = len(output_args) expect_reg_output = has_reg_output(expr.op) if n_output_args == 0: raise QASMUnsupportedError("Expression has no output.") - elif n_output_args == 1: + if n_output_args == 1: output_arg = output_args[0] output_repr = output_arg.reg_name if expect_reg_output else str(output_arg) else: @@ -1803,6 +1837,8 @@ def add_wired_clexpr(self, op: ClExprOp, args: List[Bit]) -> None: for creg in all_cregs: if creg.to_list() == output_args: output_repr = creg.name + break + assert output_repr is not None self.strings.add_string(f"{output_repr} = ") # 3. Write the right-hand side of the assignment. self.strings.add_string( @@ -1810,9 +1846,9 @@ def add_wired_clexpr(self, op: ClExprOp, args: List[Bit]) -> None: ) self.strings.add_string(";\n") - def add_wasm(self, op: WASMOp, args: List[Bit]) -> None: - inputs: List[str] = [] - outputs: List[str] = [] + def add_wasm(self, op: WASMOp, args: list[Bit]) -> None: + inputs: list[str] = [] + outputs: list[str] = [] for reglist, sizes in [(inputs, op.input_widths), (outputs, op.output_widths)]: for in_width in sizes: bits = args[:in_width] @@ -1831,7 +1867,7 @@ def add_measure(self, args: Sequence[UnitID]) -> None: label = self.strings.add_string(f"measure {args[0]} -> {args[1]};\n") self.mark_as_written(label, f"{args[1]}") - def add_zzphase(self, param: Union[float, Expr], args: Sequence[UnitID]) -> None: + def add_zzphase(self, param: float | Expr, args: Sequence[UnitID]) -> None: # as op.params returns reduced parameters, we can assume # that 0 <= param < 4 if param > 1: @@ -1865,7 +1901,7 @@ def add_gate_params(self, op: Op, args: Sequence[UnitID]) -> None: self.write_params(params) self.write_args(args) - def add_extra_noparams(self, op: Op, args: Sequence[UnitID]) -> Tuple[str, str]: + def add_extra_noparams(self, op: Op, args: Sequence[UnitID]) -> tuple[str, str]: optype = op.type opstr = _tk_to_qasm_extra_noparams[optype] gatedefstr = "" @@ -1875,7 +1911,7 @@ def add_extra_noparams(self, op: Op, args: Sequence[UnitID]) -> Tuple[str, str]: mainstr = opstr + " " + make_args_str(args) return gatedefstr, mainstr - def add_extra_params(self, op: Op, args: Sequence[UnitID]) -> Tuple[str, str]: + def add_extra_params(self, op: Op, args: Sequence[UnitID]) -> tuple[str, str]: optype, params = _get_optype_and_params(op) assert params is not None opstr = _tk_to_qasm_extra_params[optype] @@ -1892,7 +1928,7 @@ def add_op(self, op: Op, args: Sequence[UnitID]) -> None: optype, _params = _get_optype_and_params(op) if optype == OpType.RangePredicate: assert isinstance(op, RangePredicateOp) - self.add_range_predicate(op, cast(List[Bit], args)) + self.add_range_predicate(op, cast(list[Bit], args)) elif optype == OpType.Conditional: assert isinstance(op, Conditional) self.add_conditional(op, args) @@ -1901,24 +1937,24 @@ def add_op(self, op: Op, args: Sequence[UnitID]) -> None: pass elif optype == OpType.SetBits: assert isinstance(op, SetBitsOp) - self.add_set_bits(op, cast(List[Bit], args)) + self.add_set_bits(op, cast(list[Bit], args)) elif optype == OpType.CopyBits: assert isinstance(op, CopyBitsOp) - self.add_copy_bits(op, cast(List[Bit], args)) + self.add_copy_bits(op, cast(list[Bit], args)) elif optype == OpType.MultiBit: assert isinstance(op, MultiBitOp) - self.add_multi_bit(op, cast(List[Bit], args)) + self.add_multi_bit(op, cast(list[Bit], args)) elif optype in (OpType.ExplicitPredicate, OpType.ExplicitModifier): - self.add_explicit_op(op, cast(List[Bit], args)) + self.add_explicit_op(op, cast(list[Bit], args)) elif optype == OpType.ClassicalExpBox: assert isinstance(op, ClassicalExpBox) - self.add_classical_exp_box(op, cast(List[Bit], args)) + self.add_classical_exp_box(op, cast(list[Bit], args)) elif optype == OpType.ClExpr: assert isinstance(op, ClExprOp) - self.add_wired_clexpr(op, cast(List[Bit], args)) + self.add_wired_clexpr(op, cast(list[Bit], args)) elif optype == OpType.WASM: assert isinstance(op, WASMOp) - self.add_wasm(op, cast(List[Bit], args)) + self.add_wasm(op, cast(list[Bit], args)) elif optype == OpType.Measure: self.add_measure(args) elif hqs_header(self.header) and optype == OpType.ZZPhase: @@ -1947,9 +1983,7 @@ def add_op(self, op: Op, args: Sequence[UnitID]) -> None: self.gatedefs += gatedefstr self.strings.add_string(mainstr) else: - raise QASMUnsupportedError( - "Cannot print command of type: {}".format(op.get_name()) - ) + raise QASMUnsupportedError(f"Cannot print command of type: {op.get_name()}") def finalize(self) -> str: # try removing unused predicates @@ -1981,7 +2015,7 @@ def circuit_to_qasm_io( circ: Circuit, stream_out: TextIO, header: str = "qelib1", - include_gate_defs: Optional[Set[str]] = None, + include_gate_defs: set[str] | None = None, maxwidth: int = 32, ) -> None: """Convert a Circuit to QASM and write to a text stream. diff --git a/pytket/pytket/quipper/quipper.py b/pytket/pytket/quipper/quipper.py index 5ec0e20736..57ad639bd0 100644 --- a/pytket/pytket/quipper/quipper.py +++ b/pytket/pytket/quipper/quipper.py @@ -12,24 +12,33 @@ # See the License for the specific language governing permissions and # limitations under the License. -from enum import unique, Enum -from typing import List, NamedTuple, Tuple +from enum import Enum, unique from math import pi +from typing import NamedTuple from lark import Lark, Transformer, Tree -from pytket.circuit import Circuit, OpType, CircBox + +from pytket.circuit import CircBox, Circuit, OpType # The Lark grammar, transformer and type definitions below are adapted from the # code in Eddie Schoute's `quippy` project # (https://github.com/eddieschoute/quippy). The main enhancements are support # for multi-qubit gates and correct handling of negative controls on qubit 0. + # Types -Wire = NamedTuple("Wire", [("i", int)]) -ControlWire = NamedTuple("ControlWire", [("wire", Wire), ("negative", bool)]) -Control = NamedTuple( - "Control", [("controlled", List[ControlWire]), ("no_control", bool)] -) +class Wire(NamedTuple): + i: int + + +class ControlWire(NamedTuple): + wire: Wire + negative: bool + + +class Control(NamedTuple): + controlled: list[ControlWire] + no_control: bool @unique @@ -38,9 +47,9 @@ class TypeAssignment_Type(Enum): Cbit = 2 -TypeAssignment = NamedTuple( - "TypeAssignment", [("wire", Wire), ("type", TypeAssignment_Type)] -) +class TypeAssignment(NamedTuple): + wire: Wire + type: TypeAssignment_Type class Gate: @@ -71,7 +80,7 @@ class QGate( [ ("op", QGate_Op), ("inverted", bool), - ("wires", List[Wire]), + ("wires", list[Wire]), ("control", Control), ], ), @@ -132,8 +141,8 @@ class SubroutineCall( ("name", str), ("shape", str), ("inverted", bool), - ("inputs", List[Wire]), - ("outputs", List[Wire]), + ("inputs", list[Wire]), + ("outputs", list[Wire]), ("control", Control), ], ), @@ -148,21 +157,17 @@ class Comment( [ ("comment", str), ("inverted", bool), - ("wire_comments", List[Tuple[Wire, str]]), + ("wire_comments", list[tuple[Wire, str]]), ], ), ): pass -Program = NamedTuple( - "Program", - [ - ("inputs", List[TypeAssignment]), - ("gates", List[Gate]), - ("outputs", List[TypeAssignment]), - ], -) +class Program(NamedTuple): + inputs: list[TypeAssignment] + gates: list[Gate] + outputs: list[TypeAssignment] @unique @@ -172,55 +177,55 @@ class Subroutine_Control(Enum): classically = 3 -Subroutine = NamedTuple( - "Subroutine", - [ - ("name", str), - ("shape", str), - ("controllable", Subroutine_Control), - ("circuit", Program), - ], -) -Start = NamedTuple("Start", [("circuit", Program), ("subroutines", List[Subroutine])]) +class Subroutine(NamedTuple): + name: str + shape: str + controllable: Subroutine_Control + circuit: Program + + +class Start(NamedTuple): + circuit: Program + subroutines: list[Subroutine] # Transformer class QuipperTransformer(Transformer): - def int(self, t: List) -> int: + def int(self, t: list) -> int: return int(t[0]) - def float(self, t: List) -> float: + def float(self, t: list) -> float: return float(t[0]) - def string(self, t: List) -> str: + def string(self, t: list) -> str: return str(t[0][1:-1]) - def wire(self, t: List) -> Wire: + def wire(self, t: list) -> Wire: return Wire(t[0]) wire_list = list - def wire_string_list(self, t: List) -> List[Tuple[Wire, str]]: + def wire_string_list(self, t: list) -> list[tuple[Wire, str]]: wires = (el for i, el in enumerate(t) if i % 2 == 0) labels = (el for i, el in enumerate(t) if i % 2 == 1) return list(zip(wires, labels)) - def pos_control_wire(self, t: List) -> ControlWire: + def pos_control_wire(self, t: list) -> ControlWire: return ControlWire(t[0], False) control_wire_list = list - def neg_control_wire(self, t: List) -> ControlWire: + def neg_control_wire(self, t: list) -> ControlWire: return ControlWire(t[0], True) - def type_assignment(self, t: List) -> TypeAssignment: + def type_assignment(self, t: list) -> TypeAssignment: ty = TypeAssignment_Type.Qbit if t[1] == "Qbit" else TypeAssignment_Type.Cbit return TypeAssignment(t[0], ty) - def arity(self, t: List) -> List[Tree]: + def arity(self, t: list) -> list[Tree]: return list(t) - def qgate(self, t: List) -> QGate: + def qgate(self, t: list) -> QGate: ops = QGate_Op n = t[0] if n == "not" or n == "x" or n == "X": @@ -250,41 +255,41 @@ def qgate(self, t: List) -> QGate: elif n == "W": op = ops.W else: - raise RuntimeError("Unknown QGate operation: {}".format(n)) + raise RuntimeError(f"Unknown QGate operation: {n}") return QGate(op=op, inverted=len(t[1].children) > 0, wires=t[2], control=t[3]) - def qrot1(self, t: List) -> QRot: + def qrot1(self, t: list) -> QRot: return QRot( op=QRot_Op.ExpZt, timestep=t[0], inverted=len(t[1].children) > 0, wire=t[2] ) - def qrot2(self, t: List) -> QRot: + def qrot2(self, t: list) -> QRot: return QRot( op=QRot_Op.R, timestep=t[0], inverted=len(t[1].children) > 0, wire=t[2] ) - def qinit(self, t: List) -> QInit: + def qinit(self, t: list) -> QInit: return QInit(value=(t[0] == "QInit1"), wire=t[1]) - def cinit(self, t: List) -> CInit: + def cinit(self, t: list) -> CInit: return CInit(value=(t[0] == "CInit1"), wire=t[1]) - def qterm(self, t: List) -> QTerm: + def qterm(self, t: list) -> QTerm: return QTerm(value=(t[0] == "QTerm1"), wire=t[1]) - def cterm(self, t: List) -> CTerm: + def cterm(self, t: list) -> CTerm: return CTerm(value=(t[0] == "CTerm1"), wire=t[1]) - def qmeas(self, t: List) -> QMeas: + def qmeas(self, t: list) -> QMeas: return QMeas(wire=t[0]) - def qdiscard(self, t: List) -> QDiscard: + def qdiscard(self, t: list) -> QDiscard: return QDiscard(wire=t[0]) - def cdiscard(self, t: List) -> CDiscard: + def cdiscard(self, t: list) -> CDiscard: return CDiscard(wire=t[0]) - def subroutine_call(self, t: List) -> SubroutineCall: + def subroutine_call(self, t: list) -> SubroutineCall: repetitions = 1 if t[0] is not None: assert isinstance(t[0], int) @@ -299,13 +304,13 @@ def subroutine_call(self, t: List) -> SubroutineCall: control=t[6], ) - def comment(self, t: List) -> Comment: + def comment(self, t: list) -> Comment: wire_comments = t[2] if len(t) > 2 else [] return Comment( comment=t[0], inverted=len(t[1].children) > 0, wire_comments=wire_comments ) - def control_app(self, t: List) -> Control: + def control_app(self, t: list) -> Control: if not t: return Control(controlled=list(), no_control=False) if len(t) == 2: @@ -314,10 +319,10 @@ def control_app(self, t: List) -> Control: return Control(controlled=list(), no_control=True) return Control(controlled=t[0], no_control=False) - def circuit(self, t: List) -> Program: + def circuit(self, t: list) -> Program: return Program(inputs=t[0], gates=t[1:-1], outputs=t[-1]) - def subroutine(self, t: List) -> Subroutine: + def subroutine(self, t: list) -> Subroutine: if t[2] == "yes": controllable = Subroutine_Control.yes elif t[2] == "no": @@ -328,7 +333,7 @@ def subroutine(self, t: List) -> Subroutine: name=t[0], shape=t[1], controllable=controllable, circuit=t[3] ) - def start(self, t: List) -> Start: + def start(self, t: list) -> Start: circuit = t.pop(0) return Start(circuit, list(t)) @@ -337,16 +342,15 @@ def start(self, t: List) -> Start: def allowed(op: str, arity: int) -> bool: if op in ["Not", "IX", "H", "Y", "Z", "S", "T", "E", "Omega", "V"]: return arity == 1 - elif op in ["Swap", "W"]: + if op in ["Swap", "W"]: return arity == 2 - else: - # MultiNot - return True + # MultiNot + return True # Class for constructing a pytket Circuit from a parsed Quipper program class CircuitMaker: - def __init__(self, subr: List[Subroutine]) -> None: + def __init__(self, subr: list[Subroutine]) -> None: self.subrd = dict((s.name, s) for s in subr) if len(self.subrd) != len(subr): raise TypeError("Repeated subroutine names") @@ -624,7 +628,7 @@ def circuit_from_quipper(input_file: str) -> Circuit: """ # Read Quipper program from file. - with open(input_file, "r") as f: + with open(input_file) as f: quip = f.read() # Parse the circuit using the QuipperTransformer. diff --git a/pytket/pytket/transform/__init__.py b/pytket/pytket/transform/__init__.py index cce39aebbd..4929ae900a 100644 --- a/pytket/pytket/transform/__init__.py +++ b/pytket/pytket/transform/__init__.py @@ -17,5 +17,5 @@ Exports class Transform """ -from pytket._tket.transform import * from pytket._tket.circuit import CXConfigType +from pytket._tket.transform import * diff --git a/pytket/pytket/unit_id/__init__.py b/pytket/pytket/unit_id/__init__.py index 5b3961a31d..b1016c6851 100644 --- a/pytket/pytket/unit_id/__init__.py +++ b/pytket/pytket/unit_id/__init__.py @@ -13,9 +13,14 @@ # limitations under the License. from pytket._tket.unit_id import * -from pytket._tket.unit_id import Bit, BitRegister, Qubit, QubitRegister -from pytket._tket.unit_id import _TEMP_BIT_NAME -from pytket._tket.unit_id import _TEMP_BIT_REG_BASE +from pytket._tket.unit_id import ( + _TEMP_BIT_NAME, + _TEMP_BIT_REG_BASE, + Bit, + BitRegister, + Qubit, + QubitRegister, +) def _bitregister_next(self: BitRegister) -> Bit: @@ -23,8 +28,7 @@ def _bitregister_next(self: BitRegister) -> Bit: result = self[self._current] self._current += 1 return result - else: - raise StopIteration + raise StopIteration def _qubitregister_next(self: QubitRegister) -> Qubit: @@ -32,8 +36,7 @@ def _qubitregister_next(self: QubitRegister) -> Qubit: result = self[self._current] self._current += 1 return result - else: - raise StopIteration + raise StopIteration setattr(BitRegister, "__next__", _bitregister_next) diff --git a/pytket/pytket/utils/__init__.py b/pytket/pytket/utils/__init__.py index 9d07a45e64..7261395951 100644 --- a/pytket/pytket/utils/__init__.py +++ b/pytket/pytket/utils/__init__.py @@ -14,32 +14,32 @@ """Utility functions for performing high-level procedures in pytket""" +from .distribution import ( + EmpiricalDistribution, + ProbabilityDistribution, + convex_combination, +) from .expectations import ( - expectation_from_shots, expectation_from_counts, - get_pauli_expectation_value, + expectation_from_shots, get_operator_expectation_value, + get_pauli_expectation_value, ) +from .graph import Graph from .measurements import append_pauli_measurement +from .operators import QubitPauliOperator +from .outcomearray import OutcomeArray, readout_counts from .prepare import prepare_circuit from .results import ( + compare_statevectors, + compare_unitaries, counts_from_shot_table, - probs_from_counts, - probs_from_state, - permute_qubits_in_statevector, permute_basis_indexing, + permute_qubits_in_statevector, permute_rows_cols_in_unitary, - compare_statevectors, - compare_unitaries, -) -from .term_sequence import gen_term_sequence_circuit -from .operators import QubitPauliOperator -from .outcomearray import OutcomeArray, readout_counts -from .graph import Graph -from .symbolic import circuit_to_symbolic_unitary, circuit_apply_symbolic_statevector -from .distribution import ( - ProbabilityDistribution, - EmpiricalDistribution, - convex_combination, + probs_from_counts, + probs_from_state, ) from .stats import gate_counts +from .symbolic import circuit_apply_symbolic_statevector, circuit_to_symbolic_unitary +from .term_sequence import gen_term_sequence_circuit diff --git a/pytket/pytket/utils/distribution.py b/pytket/pytket/utils/distribution.py index 9985f6c80d..1b3e1dec6d 100644 --- a/pytket/pytket/utils/distribution.py +++ b/pytket/pytket/utils/distribution.py @@ -12,21 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from collections import defaultdict -from typing import ( - Any, - Callable, - DefaultDict, - Dict, - List, - Set, - Tuple, - Union, - Generic, - TypeVar, - Counter, -) import warnings +from collections import Counter, defaultdict +from collections.abc import Callable +from typing import Any, Generic, TypeVar, Union + import numpy as np from scipy.stats import rv_discrete @@ -67,7 +57,7 @@ def total(self) -> int: return self._C.total() @property - def support(self) -> Set[T0]: + def support(self) -> set[T0]: """Return the support of the distribution (set of all observations).""" return set(self._C.keys()) @@ -78,7 +68,7 @@ def __eq__(self, other: object) -> bool: return self._C == other._C def __repr__(self) -> str: - return f"{self.__class__.__name__}({repr(self._C)})" + return f"{self.__class__.__name__}({self._C!r})" def __getitem__(self, x: T0) -> int: """Get the count associated with an observation.""" @@ -150,7 +140,7 @@ class ProbabilityDistribution(Generic[T0]): derived from an :py:class:`EmpriricalDistribution`. """ - def __init__(self, P: Dict[T0, float], min_p: float = 0.0): + def __init__(self, P: dict[T0, float], min_p: float = 0.0): """Initialize with a dictionary of probabilities. :param P: Dictionary of probabilities. @@ -174,11 +164,11 @@ def __init__(self, P: Dict[T0, float], min_p: float = 0.0): s = sum(newP.values()) self._P = {x: p / s for x, p in newP.items()} - def as_dict(self) -> Dict[T0, float]: + def as_dict(self) -> dict[T0, float]: """Return the distribution as a :py:class:`dict` object.""" return self._P - def as_rv_discrete(self) -> Tuple[rv_discrete, List[T0]]: + def as_rv_discrete(self) -> tuple[rv_discrete, list[T0]]: """Return the distribution as a :py:class:`scipy.stats.rv_discrete` object. This method returns an RV over integers {0, 1, ..., k-1} where k is the size of @@ -189,7 +179,7 @@ def as_rv_discrete(self) -> Tuple[rv_discrete, List[T0]]: return (rv_discrete(values=(range(len(X)), [self._P[x] for x in X])), X) @property - def support(self) -> Set[T0]: + def support(self) -> set[T0]: """Return the support of the distribution (set of all possible outcomes).""" return set(self._P.keys()) @@ -204,7 +194,7 @@ def __eq__(self, other: object) -> bool: return all(np.isclose(self._P[x], other._P[x]) for x in keys0) def __repr__(self) -> str: - return f"{self.__class__.__name__}({repr(self._P)})" + return f"{self.__class__.__name__}({self._P!r})" def __getitem__(self, x: T0) -> float: """Get the probability associated with a possible outcome.""" @@ -245,7 +235,7 @@ def map(self, mapping: Callable[[T0], T1]) -> "ProbabilityDistribution[T1]": :param mapping: A function defined on all possible outcomes, mapping them to another domain. """ - P: DefaultDict[Any, float] = defaultdict(float) + P: defaultdict[Any, float] = defaultdict(float) for x, p in self._P.items(): P[mapping(x)] += p return ProbabilityDistribution(P) @@ -271,7 +261,7 @@ def variance(self, f: Callable[[T0], Number]) -> Number: def convex_combination( - dists: List[Tuple[ProbabilityDistribution[T0], float]] + dists: list[tuple[ProbabilityDistribution[T0], float]] ) -> ProbabilityDistribution[T0]: """Return a convex combination of probability distributions. @@ -286,7 +276,7 @@ def convex_combination( >>> dist3.expectation(lambda x : x**2) 0.75 """ - P: DefaultDict[T0, float] = defaultdict(float) + P: defaultdict[T0, float] = defaultdict(float) S = 0.0 for pd, a in dists: if a < 0: diff --git a/pytket/pytket/utils/expectations.py b/pytket/pytket/utils/expectations.py index 95cc093bb9..76615ad0d4 100644 --- a/pytket/pytket/utils/expectations.py +++ b/pytket/pytket/utils/expectations.py @@ -12,20 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import TYPE_CHECKING, Dict, Optional, Tuple, List +from typing import TYPE_CHECKING import numpy as np + from pytket.circuit import Circuit, Qubit -from pytket.pauli import QubitPauliString from pytket.partition import ( - measurement_reduction, - PauliPartitionStrat, GraphColourMethod, + PauliPartitionStrat, + measurement_reduction, ) +from pytket.pauli import QubitPauliString from .measurements import _all_pauli_measurements, append_pauli_measurement -from .results import KwargTypes from .operators import QubitPauliOperator +from .results import KwargTypes if TYPE_CHECKING: from pytket.backends.backend import Backend @@ -47,7 +48,7 @@ def expectation_from_shots(shot_table: np.ndarray) -> float: return -2 * aritysum / len(shot_table) + 1 -def expectation_from_counts(counts: Dict[Tuple[int, ...], int]) -> float: +def expectation_from_counts(counts: dict[tuple[int, ...], int]) -> float: """Estimates the expectation value of a circuit from shot counts. Computes the parity of '1's across all bits to determine a +1 or -1 contribution from each readout, and returns the weighted average. @@ -75,7 +76,7 @@ def get_pauli_expectation_value( state_circuit: Circuit, pauli: QubitPauliString, backend: "Backend", - n_shots: Optional[int] = None, + n_shots: int | None = None, ) -> complex: """Estimates the expectation value of the given circuit with respect to the Pauli term by preparing measurements in the appropriate basis, running on the backend and @@ -108,19 +109,18 @@ def get_pauli_expectation_value( if backend.supports_counts: counts = backend.run_circuit(measured_circ, n_shots=n_shots).get_counts() return expectation_from_counts(counts) - elif backend.supports_shots: + if backend.supports_shots: shot_table = backend.run_circuit(measured_circ, n_shots=n_shots).get_shots() return expectation_from_shots(shot_table) - else: - raise ValueError("Backend does not support counts or shots") + raise ValueError("Backend does not support counts or shots") def get_operator_expectation_value( state_circuit: Circuit, operator: QubitPauliOperator, backend: "Backend", - n_shots: Optional[int] = None, - partition_strat: Optional[PauliPartitionStrat] = None, + n_shots: int | None = None, + partition_strat: PauliPartitionStrat | None = None, colour_method: GraphColourMethod = GraphColourMethod.LargestFirst, **kwargs: KwargTypes, ) -> complex: @@ -150,7 +150,7 @@ def get_operator_expectation_value( if not backend.valid_circuit(state_circuit): state_circuit = backend.get_compiled_circuit(state_circuit) try: - coeffs: List[complex] = [complex(v) for v in operator._dict.values()] + coeffs: list[complex] = [complex(v) for v in operator._dict.values()] except TypeError: raise ValueError("QubitPauliOperator contains unevaluated symbols.") if backend.supports_expectation and ( @@ -189,64 +189,62 @@ def get_operator_expectation_value( for handle in handles: backend.pop_result(handle) return energy - elif backend.supports_shots: + if backend.supports_shots: for result, coeff in zip(results, coeffs): shots = result.get_shots() energy += coeff * expectation_from_shots(shots) for handle in handles: backend.pop_result(handle) return energy - else: - raise ValueError("Backend does not support counts or shots") - else: - qubit_pauli_string_list = [p for p in operator._dict.keys() if (p != id_string)] - measurement_expectation = measurement_reduction( - qubit_pauli_string_list, partition_strat, colour_method - ) - # note: this implementation requires storing all the results - # in memory simultaneously to filter through them. - measure_circs = [] - for pauli_circ in measurement_expectation.measurement_circs: - circ = state_circuit.copy() - circ.append(pauli_circ) - measure_circs.append(circ) - handles = backend.process_circuits( - backend.get_compiled_circuits(measure_circs), - n_shots=n_shots, - valid_check=True, - **kwargs, - ) - results = backend.get_results(handles) - for pauli_string in measurement_expectation.results: - bitmaps = measurement_expectation.results[pauli_string] - string_coeff = operator[pauli_string] - for bm in bitmaps: - index = bm.circ_index - aritysum = 0.0 - if backend.supports_counts: - counts = results[index].get_counts() - total_shots = 0 - for row, count in counts.items(): - aritysum += count * (sum(row[i] for i in bm.bits) % 2) - total_shots += count - e = ( - ((-1) ** bm.invert) - * string_coeff - * (-2 * aritysum / total_shots + 1) - ) - energy += complex(e) - elif backend.supports_shots: - shots = results[index].get_shots() - for row in shots: - aritysum += sum(row[i] for i in bm.bits) % 2 - e = ( - ((-1) ** bm.invert) - * string_coeff - * (-2 * aritysum / len(shots) + 1) - ) - energy += complex(e) - else: - raise ValueError("Backend does not support counts or shots") - for handle in handles: - backend.pop_result(handle) - return energy + raise ValueError("Backend does not support counts or shots") + qubit_pauli_string_list = [p for p in operator._dict.keys() if (p != id_string)] + measurement_expectation = measurement_reduction( + qubit_pauli_string_list, partition_strat, colour_method + ) + # note: this implementation requires storing all the results + # in memory simultaneously to filter through them. + measure_circs = [] + for pauli_circ in measurement_expectation.measurement_circs: + circ = state_circuit.copy() + circ.append(pauli_circ) + measure_circs.append(circ) + handles = backend.process_circuits( + backend.get_compiled_circuits(measure_circs), + n_shots=n_shots, + valid_check=True, + **kwargs, + ) + results = backend.get_results(handles) + for pauli_string in measurement_expectation.results: + bitmaps = measurement_expectation.results[pauli_string] + string_coeff = operator[pauli_string] + for bm in bitmaps: + index = bm.circ_index + aritysum = 0.0 + if backend.supports_counts: + counts = results[index].get_counts() + total_shots = 0 + for row, count in counts.items(): + aritysum += count * (sum(row[i] for i in bm.bits) % 2) + total_shots += count + e = ( + ((-1) ** bm.invert) + * string_coeff + * (-2 * aritysum / total_shots + 1) + ) + energy += complex(e) + elif backend.supports_shots: + shots = results[index].get_shots() + for row in shots: + aritysum += sum(row[i] for i in bm.bits) % 2 + e = ( + ((-1) ** bm.invert) + * string_coeff + * (-2 * aritysum / len(shots) + 1) + ) + energy += complex(e) + else: + raise ValueError("Backend does not support counts or shots") + for handle in handles: + backend.pop_result(handle) + return energy diff --git a/pytket/pytket/utils/graph.py b/pytket/pytket/utils/graph.py index 16467bd371..f8642763f0 100644 --- a/pytket/pytket/utils/graph.py +++ b/pytket/pytket/utils/graph.py @@ -15,10 +15,9 @@ from collections import defaultdict from itertools import combinations from tempfile import NamedTemporaryFile -from typing import Optional -import networkx as nx # type: ignore import graphviz as gv # type: ignore +import networkx as nx # type: ignore from pytket.circuit import Circuit @@ -57,9 +56,9 @@ def __init__(self, c: Circuit): self.input_names = input_names self.output_names = output_names self.node_data = node_data - self.Gnx: Optional[nx.MultiDiGraph] = None - self.G: Optional[gv.Digraph] = None - self.Gqc: Optional[gv.Graph] = None + self.Gnx: nx.MultiDiGraph | None = None + self.G: gv.Digraph | None = None + self.Gqc: gv.Graph | None = None self.edge_data: dict[tuple[int, int], list[tuple[int, int, str]]] = defaultdict( list ) diff --git a/pytket/pytket/utils/measurements.py b/pytket/pytket/utils/measurements.py index ce5db31b41..be78eeba34 100644 --- a/pytket/pytket/utils/measurements.py +++ b/pytket/pytket/utils/measurements.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Iterable -from pytket.circuit import Circuit, Bit +from collections.abc import Iterable + +from pytket.circuit import Bit, Circuit from pytket.pauli import Pauli, QubitPauliString + from .operators import QubitPauliOperator diff --git a/pytket/pytket/utils/operators.py b/pytket/pytket/utils/operators.py index 8547ab524e..ce37719e8e 100644 --- a/pytket/pytket/utils/operators.py +++ b/pytket/pytket/utils/operators.py @@ -13,23 +13,23 @@ # limitations under the License. import copy -from typing import Dict, TYPE_CHECKING, Union, List, Optional, Set, Any +from typing import TYPE_CHECKING, Any, Union import numpy import numpy as np -from sympy import Symbol, sympify, Expr, re, im -from pytket.pauli import QubitPauliString, pauli_string_mult +from sympy import Expr, Symbol, im, re, sympify + from pytket.circuit import Qubit +from pytket.pauli import QubitPauliString, pauli_string_mult from pytket.utils.serialization import complex_to_list, list_to_complex - CoeffTypeAccepted = Union[int, float, complex, Expr] if TYPE_CHECKING: from scipy.sparse import csc_matrix -def _coeff_convert(coeff: Union[CoeffTypeAccepted, str]) -> Expr: +def _coeff_convert(coeff: CoeffTypeAccepted | str) -> Expr: sympy_val = sympify(coeff) if not isinstance(sympy_val, Expr): raise ValueError("Unsupported value for QubitPauliString coefficient") @@ -62,9 +62,9 @@ class QubitPauliOperator: def __init__( self, - dictionary: Optional[Dict[QubitPauliString, CoeffTypeAccepted]] = None, + dictionary: dict[QubitPauliString, CoeffTypeAccepted] | None = None, ) -> None: - self._dict: Dict[QubitPauliString, Expr] = dict() + self._dict: dict[QubitPauliString, Expr] = dict() if dictionary: for key, value in dictionary.items(): self._dict[key] = _coeff_convert(value) @@ -91,10 +91,10 @@ def __setitem__(self, key: QubitPauliString, value: CoeffTypeAccepted) -> None: self._dict[key] = _coeff_convert(value) self._all_qubits.update(key.map.keys()) - def __getstate__(self) -> Dict[QubitPauliString, Expr]: + def __getstate__(self) -> dict[QubitPauliString, Expr]: return self._dict - def __setstate__(self, _dict: Dict[QubitPauliString, Expr]) -> None: + def __setstate__(self, _dict: dict[QubitPauliString, Expr]) -> None: # values assumed to be already sympified self._dict = _dict self._collect_qubits() @@ -117,7 +117,7 @@ def __iadd__(self, addend: "QubitPauliOperator") -> "QubitPauliOperator": self[key] = self.get(key, 0.0) + value self._all_qubits.update(addend._all_qubits) else: - raise TypeError("Cannot add {} to QubitPauliOperator.".format(type(addend))) + raise TypeError(f"Cannot add {type(addend)} to QubitPauliOperator.") return self @@ -147,7 +147,7 @@ def __imul__( # Handle operator of the same type if isinstance(multiplier, QubitPauliOperator): - result_terms: Dict = dict() + result_terms: dict = dict() for left_key, left_value in self._dict.items(): for right_key, right_value in multiplier._dict.items(): new_term, bonus_coeff = pauli_string_mult(left_key, right_key) @@ -163,16 +163,13 @@ def __imul__( return self # Handle scalars. - elif isinstance(multiplier, (float, Expr)): + if isinstance(multiplier, (float, Expr)): for key in self._dict: self[key] *= multiplier return self # Invalid multiplier type - else: - raise TypeError( - "Cannot multiply QubitPauliOperator with {}".format(type(multiplier)) - ) + raise TypeError(f"Cannot multiply QubitPauliOperator with {type(multiplier)}") def __mul__( self, multiplier: Union[float, Expr, "QubitPauliOperator"] @@ -202,7 +199,7 @@ def __rmul__(self, multiplier: CoeffTypeAccepted) -> "QubitPauliOperator": return self.__mul__(_coeff_convert(multiplier)) @property - def all_qubits(self) -> Set[Qubit]: + def all_qubits(self) -> set[Qubit]: """ :return: The set of all qubits the operator ranges over (including qubits that were provided explicitly as identities) @@ -210,7 +207,7 @@ def all_qubits(self) -> Set[Qubit]: """ return self._all_qubits - def subs(self, symbol_dict: Dict[Symbol, complex]) -> None: + def subs(self, symbol_dict: dict[Symbol, complex]) -> None: """Substitutes any matching symbols in the QubitPauliOperator. :param symbol_dict: A dictionary of symbols to fixed values. @@ -219,19 +216,19 @@ def subs(self, symbol_dict: Dict[Symbol, complex]) -> None: for key, value in self._dict.items(): self._dict[key] = value.subs(symbol_dict) - def to_list(self) -> List[Dict[str, Any]]: + def to_list(self) -> list[dict[str, Any]]: """Generate a list serialized representation of QubitPauliOperator, suitable for writing to JSON. :return: JSON serializable list of dictionaries. :rtype: List[Dict[str, Any]] """ - ret: List[Dict[str, Any]] = [] + ret: list[dict[str, Any]] = [] for k, v in self._dict.items(): try: coeff = complex_to_list(complex(v)) except TypeError: - assert type(Expr(v)) == Expr + assert isinstance(Expr(v), Expr) coeff = str(v) ret.append( { @@ -242,7 +239,7 @@ def to_list(self) -> List[Dict[str, Any]]: return ret @classmethod - def from_list(cls, pauli_list: List[Dict[str, Any]]) -> "QubitPauliOperator": + def from_list(cls, pauli_list: list[dict[str, Any]]) -> "QubitPauliOperator": """Construct a QubitPauliOperator from a serializable JSON list format, as returned by QubitPauliOperator.to_list() @@ -250,21 +247,18 @@ def from_list(cls, pauli_list: List[Dict[str, Any]]) -> "QubitPauliOperator": :rtype: QubitPauliOperator """ - def get_qps(obj: Dict[str, Any]) -> QubitPauliString: + def get_qps(obj: dict[str, Any]) -> QubitPauliString: return QubitPauliString.from_list(obj["string"]) - def get_coeff(obj: Dict[str, Any]) -> Expr: + def get_coeff(obj: dict[str, Any]) -> Expr: coeff = obj["coefficient"] if type(coeff) is str: return _coeff_convert(coeff) - else: - return _coeff_convert(list_to_complex(coeff)) + return _coeff_convert(list_to_complex(coeff)) return QubitPauliOperator({get_qps(obj): get_coeff(obj) for obj in pauli_list}) - def to_sparse_matrix( - self, qubits: Union[List[Qubit], int, None] = None - ) -> "csc_matrix": + def to_sparse_matrix(self, qubits: list[Qubit] | int | None = None) -> "csc_matrix": """Represents the sparse operator as a dense operator under the ordering scheme specified by ``qubits``, and generates the corresponding matrix. @@ -297,7 +291,7 @@ def to_sparse_matrix( ) def dot_state( - self, state: np.ndarray, qubits: Optional[List[Qubit]] = None + self, state: np.ndarray, qubits: list[Qubit] | None = None ) -> np.ndarray: """Applies the operator to the given state, mapping qubits to indexes according to ``qubits``. @@ -328,7 +322,7 @@ def dot_state( return product_sum if isinstance(product_sum, numpy.ndarray) else state def state_expectation( - self, state: np.ndarray, qubits: Optional[List[Qubit]] = None + self, state: np.ndarray, qubits: list[Qubit] | None = None ) -> complex: """Calculates the expectation value of the given statevector with respect to the operator, mapping qubits to indexes according to ``qubits``. diff --git a/pytket/pytket/utils/outcomearray.py b/pytket/pytket/utils/outcomearray.py index 63cdafd51a..0ecba91297 100644 --- a/pytket/pytket/utils/outcomearray.py +++ b/pytket/pytket/utils/outcomearray.py @@ -14,8 +14,10 @@ """`OutcomeArray` class and associated methods.""" import operator +from collections import Counter +from collections.abc import Sequence from functools import reduce -from typing import Counter, List, Sequence, Dict, Tuple, Any, Optional, cast +from typing import Any, cast import numpy as np import numpy.typing as npt @@ -59,7 +61,7 @@ def __array_finalize__(self, obj: Any, *args: Any, **kwargs: Any) -> None: # see InfoArray.__array_finalize__ for comments if obj is None: return - self._width: Optional[int] = getattr(obj, "_width", None) + self._width: int | None = getattr(obj, "_width", None) @property def width(self) -> int: @@ -103,7 +105,7 @@ def to_readout(self) -> np.ndarray: raise ValueError(f"Not a singleton: {self.n_outcomes} readouts") return cast(np.ndarray, self.to_readouts()[0]) - def to_intlist(self, big_endian: bool = True) -> List[int]: + def to_intlist(self, big_endian: bool = True) -> list[int]: """Express each outcome as an integer corresponding to the bit values. :param big_endian: whether to use big endian encoding (or little endian @@ -162,7 +164,7 @@ def counts(self) -> Counter["OutcomeArray"]: oalist = [OutcomeArray(x[None, :], width) for x in ars] return Counter(dict(zip(oalist, count_vals))) - def choose_indices(self, indices: List[int]) -> "OutcomeArray": + def choose_indices(self, indices: list[int]) -> "OutcomeArray": """Permute ordering of bits in outcomes or choose subset of bits. e.g. [1, 0, 2] acting on a bitstring of length 4 swaps bit locations 0 & 1, leaves 2 in the same place and deletes location 3. @@ -174,7 +176,7 @@ def choose_indices(self, indices: List[int]) -> "OutcomeArray": """ return OutcomeArray.from_readouts(self.to_readouts()[..., indices]) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Return a JSON serializable dictionary representation of the OutcomeArray. :return: JSON serializable dictionary @@ -183,7 +185,7 @@ def to_dict(self) -> Dict[str, Any]: return {"width": self.width, "array": self.tolist()} @classmethod - def from_dict(cls, ar_dict: Dict[str, Any]) -> "OutcomeArray": + def from_dict(cls, ar_dict: dict[str, Any]) -> "OutcomeArray": """Create an OutcomeArray from JSON serializable dictionary (as created by `to_dict`). @@ -199,6 +201,6 @@ def from_dict(cls, ar_dict: Dict[str, Any]) -> "OutcomeArray": def readout_counts( ctr: Counter[OutcomeArray], -) -> Counter[Tuple[int, ...]]: +) -> Counter[tuple[int, ...]]: """Convert counts from :py:class:`OutcomeArray` types to tuples of ints.""" return Counter({tuple(oa.to_readout()): n for oa, n in ctr.items()}) diff --git a/pytket/pytket/utils/prepare.py b/pytket/pytket/utils/prepare.py index 32a5889b57..4cae2f4247 100644 --- a/pytket/pytket/utils/prepare.py +++ b/pytket/pytket/utils/prepare.py @@ -12,15 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional, Tuple from pytket.circuit import Circuit from pytket.passes import ContextSimp from pytket.transform import separate_classical def prepare_circuit( - circ: Circuit, allow_classical: bool = True, xcirc: Optional[Circuit] = None -) -> Tuple[Circuit, Circuit]: + circ: Circuit, allow_classical: bool = True, xcirc: Circuit | None = None +) -> tuple[Circuit, Circuit]: """ Prepare a circuit for processing by a backend device. diff --git a/pytket/pytket/utils/results.py b/pytket/pytket/utils/results.py index 87fe3d65ec..0219b05dcb 100644 --- a/pytket/pytket/utils/results.py +++ b/pytket/pytket/utils/results.py @@ -12,13 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Dict, List, Tuple, Union +from typing import Any import numpy as np + from pytket.circuit import BasisOrder -StateTuple = Tuple[int, ...] -CountsDict = Dict[StateTuple, Union[int, float]] +StateTuple = tuple[int, ...] +CountsDict = dict[StateTuple, int | float] KwargTypes = Any @@ -29,7 +30,7 @@ class BitPermuter: """ - def __init__(self, permutation: Tuple[int, ...]): + def __init__(self, permutation: tuple[int, ...]): """Constructor :param permutation: Map from current bit index (big-endian) to its new position, @@ -42,7 +43,7 @@ def __init__(self, permutation: Tuple[int, ...]): raise ValueError("Permutation is not a valid complete permutation.") self.perm = tuple(permutation) self.n_bits = len(self.perm) - self.int_maps: Tuple[Dict[int, int], Dict[int, int]] = ({}, {}) + self.int_maps: tuple[dict[int, int], dict[int, int]] = ({}, {}) def permute(self, val: int, inverse: bool = False) -> int: """Return input with bit values permuted. @@ -71,7 +72,7 @@ def permute(self, val: int, inverse: bool = False) -> int: other_map[res] = val return res - def permute_all(self) -> List[int]: + def permute_all(self) -> list[int]: """Permute all integers within bit-width specified by permutation. :return: List of permuted outputs. @@ -80,7 +81,7 @@ def permute_all(self) -> List[int]: return list(map(self.permute, range(1 << self.n_bits))) -def counts_from_shot_table(shot_table: np.ndarray) -> Dict[Tuple[int, ...], int]: +def counts_from_shot_table(shot_table: np.ndarray) -> dict[tuple[int, ...], int]: """Summarises a shot table into a dictionary of counts for each observed outcome. :param shot_table: Table of shots from a pytket backend. @@ -93,8 +94,8 @@ def counts_from_shot_table(shot_table: np.ndarray) -> Dict[Tuple[int, ...], int] def probs_from_counts( - counts: Dict[Tuple[int, ...], int] -) -> Dict[Tuple[int, ...], float]: + counts: dict[tuple[int, ...], int] +) -> dict[tuple[int, ...], float]: """Converts raw counts of observed outcomes into the observed probability distribution. @@ -109,7 +110,7 @@ def probs_from_counts( def _index_to_readout( index: int, width: int, basis: BasisOrder = BasisOrder.ilo -) -> Tuple[int, ...]: +) -> tuple[int, ...]: return tuple( (index >> i) & 1 for i in range(width)[:: (-1) ** (basis == BasisOrder.ilo)] ) @@ -155,7 +156,7 @@ def _compute_probs_from_state(state: np.ndarray, min_p: float = 1e-10) -> np.nda def probs_from_state( state: np.ndarray, min_p: float = 1e-10 -) -> Dict[Tuple[int, ...], float]: +) -> dict[tuple[int, ...], float]: """ Converts statevector to the probability distribution over readouts in the computational basis. Ignores probabilities lower than `min_p`. @@ -172,7 +173,7 @@ def probs_from_state( return {_index_to_readout(i, width): p for i, p in enumerate(probs) if p != 0} -def int_dist_from_state(state: np.ndarray, min_p: float = 1e-10) -> Dict[int, float]: +def int_dist_from_state(state: np.ndarray, min_p: float = 1e-10) -> dict[int, float]: """ Converts statevector to the probability distribution over its indices. Ignores probabilities lower than `min_p`. @@ -204,7 +205,7 @@ def get_n_qb_from_statevector(state: np.ndarray) -> int: def _assert_compatible_state_permutation( - state: np.ndarray, permutation: Tuple[int, ...] + state: np.ndarray, permutation: tuple[int, ...] ) -> None: """Asserts that a statevector and a permutation list both refer to the same number of qubits @@ -221,7 +222,7 @@ def _assert_compatible_state_permutation( def permute_qubits_in_statevector( - state: np.ndarray, permutation: Tuple[int, ...] + state: np.ndarray, permutation: tuple[int, ...] ) -> np.ndarray: """Rearranges a statevector according to a permutation of the qubit indices. @@ -247,7 +248,7 @@ def permute_qubits_in_statevector( def permute_basis_indexing( - matrix: np.ndarray, permutation: Tuple[int, ...] + matrix: np.ndarray, permutation: tuple[int, ...] ) -> np.ndarray: """Rearranges the first dimensions of an array (statevector or unitary) according to a permutation of the bit indices in the binary representation @@ -269,7 +270,7 @@ def permute_basis_indexing( def permute_rows_cols_in_unitary( - matrix: np.ndarray, permutation: Tuple[int, ...] + matrix: np.ndarray, permutation: tuple[int, ...] ) -> np.ndarray: """Rearranges the rows of a unitary matrix according to a permutation of the qubit indices. @@ -286,8 +287,7 @@ def permute_rows_cols_in_unitary( permuter = BitPermuter(permutation) all_perms = permuter.permute_all() permat: np.ndarray = matrix[:, all_perms] - permat = permat[all_perms, :] - return permat + return permat[all_perms, :] def compare_statevectors(first: np.ndarray, second: np.ndarray) -> bool: diff --git a/pytket/pytket/utils/spam.py b/pytket/pytket/utils/spam.py index ac0b5c3208..351c391877 100644 --- a/pytket/pytket/utils/spam.py +++ b/pytket/pytket/utils/spam.py @@ -13,25 +13,26 @@ # limitations under the License. import itertools +from collections import Counter, OrderedDict +from collections.abc import Callable, Iterable from functools import lru_cache - from math import ceil, log2 -from collections import OrderedDict -from typing import Dict, Iterable, List, Tuple, Counter, cast, Optional, Callable, Union +from typing import cast + import numpy as np -from pytket.circuit import Circuit, Qubit, Bit, Node, CircBox, OpType + from pytket.backends import Backend -from pytket.passes import DecomposeBoxes, FlattenRegisters from pytket.backends.backendresult import BackendResult +from pytket.circuit import Bit, CircBox, Circuit, Node, OpType, Qubit +from pytket.passes import DecomposeBoxes, FlattenRegisters from pytket.utils.outcomearray import OutcomeArray from pytket.utils.results import CountsDict, StateTuple - -ParallelMeasures = List[Dict[Qubit, Bit]] +ParallelMeasures = list[dict[Qubit, Bit]] def compress_counts( - counts: Dict[StateTuple, float], tol: float = 1e-6, round_to_int: bool = False + counts: dict[StateTuple, float], tol: float = 1e-6, round_to_int: bool = False ) -> CountsDict: """Filter counts to remove states that have a count value (which can be a floating-point number) below a tolerance, and optionally round to an @@ -47,7 +48,7 @@ def compress_counts( :return: Filtered counts :rtype: CountsDict """ - valprocess: Callable[[float], Union[int, float]] = lambda x: ( + valprocess: Callable[[float], int | float] = lambda x: ( int(round(x)) if round_to_int else x ) processed_pairs = ( @@ -57,7 +58,7 @@ def compress_counts( @lru_cache(maxsize=128) -def binary_to_int(bintuple: Tuple[int]) -> int: +def binary_to_int(bintuple: tuple[int]) -> int: """Convert a binary tuple to corresponding integer, with most significant bit as the first element of tuple. @@ -75,7 +76,7 @@ def binary_to_int(bintuple: Tuple[int]) -> int: @lru_cache(maxsize=128) -def int_to_binary(val: int, dim: int) -> Tuple[int, ...]: +def int_to_binary(val: int, dim: int) -> tuple[int, ...]: """Convert an integer to corresponding binary tuple, with most significant bit as the first element of tuple. @@ -87,7 +88,7 @@ def int_to_binary(val: int, dim: int) -> Tuple[int, ...]: :return: Binary tuple of width dim :rtype: Tuple[int, ...] """ - return tuple(map(int, format(val, "0{}b".format(dim)))) + return tuple(map(int, format(val, f"0{dim}b"))) ######################################### @@ -98,7 +99,7 @@ def int_to_binary(val: int, dim: int) -> Tuple[int, ...]: ### and especially ### https://gist.github.com/ahwillia/f65bc70cb30206d4eadec857b98c4065 ### on which this code is based. -def _unfold(tens: np.ndarray, mode: int, dims: List[int]) -> np.ndarray: +def _unfold(tens: np.ndarray, mode: int, dims: list[int]) -> np.ndarray: """Unfolds tensor into matrix. :param tens: Tensor with shape equivalent to dimensions @@ -113,11 +114,10 @@ def _unfold(tens: np.ndarray, mode: int, dims: List[int]) -> np.ndarray: """ if mode == 0: return tens.reshape(dims[0], -1) - else: - return np.moveaxis(tens, mode, 0).reshape(dims[mode], -1) + return np.moveaxis(tens, mode, 0).reshape(dims[mode], -1) -def _refold(vec: np.ndarray, mode: int, dims: List[int]) -> np.ndarray: +def _refold(vec: np.ndarray, mode: int, dims: list[int]) -> np.ndarray: """Refolds vector into tensor. :param vec: Tensor with length equivalent to the product of dimensions given in @@ -133,11 +133,10 @@ def _refold(vec: np.ndarray, mode: int, dims: List[int]) -> np.ndarray: """ if mode == 0: return vec.reshape(dims) - else: - # Reshape and then move dims[mode] back to its - # appropriate spot (undoing the `unfold` operation). - tens = vec.reshape([dims[mode]] + [d for m, d in enumerate(dims) if m != mode]) - return np.moveaxis(tens, 0, mode) + # Reshape and then move dims[mode] back to its + # appropriate spot (undoing the `unfold` operation). + tens = vec.reshape([dims[mode]] + [d for m, d in enumerate(dims) if m != mode]) + return np.moveaxis(tens, 0, mode) def _compute_dot(submatrices: Iterable[np.ndarray], vector: np.ndarray) -> np.ndarray: @@ -204,7 +203,7 @@ def _bayesian_iterative_correct( submatrices: Iterable[np.ndarray], measurements: np.ndarray, tol: float = 1e-5, - max_it: Optional[int] = None, + max_it: int | None = None, ) -> np.ndarray: """Finds new states to represent application of inversion of submatrices on measurements. Converges when update states within tol range of previously @@ -246,7 +245,7 @@ def _bayesian_iterative_correct( return true_states -def reduce_matrix(indices_to_remove: List[int], matrix: np.ndarray) -> np.ndarray: +def reduce_matrix(indices_to_remove: list[int], matrix: np.ndarray) -> np.ndarray: """Removes indices from indices_to_remove from binary associated to indexing of matrix, producing a new transition matrix. To do so, it assigns all transition probabilities as the given state in the remaining indices binary, with the removed @@ -286,8 +285,8 @@ def reduce_matrix(indices_to_remove: List[int], matrix: np.ndarray) -> np.ndarra def reduce_matrices( - entries_to_remove: List[Tuple[int, int]], matrices: List[np.ndarray] -) -> List[np.ndarray]: + entries_to_remove: list[tuple[int, int]], matrices: list[np.ndarray] +) -> list[np.ndarray]: """Removes some dimensions from some matrices. :param entries_to_remove: Via indexing, details dimensions to be removed. @@ -298,16 +297,15 @@ def reduce_matrices( :return: Matrices with some dimensions removed. :rtype: List[np.ndarray] """ - organise: Dict[int, List] = dict({k: [] for k in range(len(matrices))}) + organise: dict[int, list] = dict({k: [] for k in range(len(matrices))}) for unused in entries_to_remove: # unused[0] is index in matrices # unused[1] is qubit index in matrix organise[unused[0]].append(unused[1]) output_matrices = [reduce_matrix(organise[m], matrices[m]) for m in organise] - normalised_mats = [ + return [ mat / np.sum(mat, axis=0) for mat in [x for x in output_matrices if len(x) != 0] ] - return normalised_mats class SpamCorrecter: @@ -318,9 +316,7 @@ class SpamCorrecter: dictionary. """ - def __init__( - self, qubit_subsets: List[List[Node]], backend: Optional[Backend] = None - ): + def __init__(self, qubit_subsets: list[list[Node]], backend: Backend | None = None): """Construct a new `SpamCorrecter`. :param qubit_subsets: A list of lists of correlated Nodes of an `Architecture`. @@ -365,7 +361,7 @@ def to_tuple(inp: list[Node]) -> tuple: self.xbox = CircBox(xcirc) - def calibration_circuits(self) -> List[Circuit]: + def calibration_circuits(self) -> list[Circuit]: """Generate calibration circuits according to the specified correlations. :return: A list of calibration circuits to be run on the machine. The circuits @@ -415,7 +411,7 @@ def calibration_circuits(self) -> List[Circuit]: self.state_infos.append((new_state_dicts, state_circuit.qubit_to_bit_map)) return self.prepared_circuits - def calculate_matrices(self, results_list: List[BackendResult]) -> None: + def calculate_matrices(self, results_list: list[BackendResult]) -> None: """Calculate the calibration matrices from the results of running calibration circuits. @@ -431,7 +427,7 @@ def calculate_matrices(self, results_list: List[BackendResult]) -> None: ) counter = 0 - self.node_index_dict: Dict[Node, Tuple[int, int]] = dict() + self.node_index_dict: dict[Node, tuple[int, int]] = dict() for qbs, dim in zip(self.subsets_matrix_map, self.subset_dimensions): # for a subset with n qubits, create a 2^n by 2^n matrix @@ -492,7 +488,7 @@ def correct_counts( result: BackendResult, parallel_measures: ParallelMeasures, method: str = "bayesian", - options: Optional[Dict] = None, + options: dict | None = None, ) -> BackendResult: """Modifies count distribution for result, such that the inversion of the pure noise map represented by characterisation matrices is applied to it. @@ -528,8 +524,7 @@ def correct_counts( # no q duplicates as mapping is dict from qubit to bit if q not in unused_qbs: raise ValueError( - "Measured qubit {} is not characterised by " - "SpamCorrecter".format(q) + f"Measured qubit {q} is not characterised by SpamCorrecter" ) unused_qbs.remove(q) # type:ignore[arg-type] char_bits_order.append(mapping[q]) @@ -601,7 +596,7 @@ def correct_counts( # produce and return BackendResult object return BackendResult(counts=counter, c_bits=char_bits_order) - def to_dict(self) -> Dict: + def to_dict(self) -> dict: """Get calibration information as a dictionary. :return: Dictionary output @@ -616,15 +611,14 @@ def to_dict(self) -> Dict: for uid in self.node_index_dict ] char_matrices = [m.tolist() for m in self.characterisation_matrices] - self_dict = { + return { "correlations": correlations, "node_index_dict": node_index_hashable, "characterisation_matrices": char_matrices, } - return self_dict @classmethod - def from_dict(class_obj, d: Dict) -> "SpamCorrecter": + def from_dict(class_obj, d: dict) -> "SpamCorrecter": """Build a `SpamCorrecter` instance from a dictionary in the format returned by `to_dict`. diff --git a/pytket/pytket/utils/stats.py b/pytket/pytket/utils/stats.py index 83ceeea426..5d1b1a1d84 100644 --- a/pytket/pytket/utils/stats.py +++ b/pytket/pytket/utils/stats.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Counter +from collections import Counter + from pytket.circuit import Circuit, OpType diff --git a/pytket/pytket/utils/symbolic.py b/pytket/pytket/utils/symbolic.py index 0107964dc1..609401094e 100644 --- a/pytket/pytket/utils/symbolic.py +++ b/pytket/pytket/utils/symbolic.py @@ -16,7 +16,8 @@ for symbolic circuits. This uses the sympy.physics.quantum module and produces sympy objects. The implementations are slow and scale poorly, so this is only suitable for very small (up to 5 qubit) circuits.""" -from typing import Callable, Dict, List, Optional, Type, Union, cast +from collections.abc import Callable +from typing import cast import numpy as np import sympy @@ -24,25 +25,25 @@ BlockDiagMatrix, BlockMatrix, Expr, + I, Identity, ImmutableMatrix, Matrix, Mul, - I, diag, eye, zeros, ) from sympy.physics.quantum import gate as symgate from sympy.physics.quantum import represent -from sympy.physics.quantum.tensorproduct import matrix_tensor_product from sympy.physics.quantum.qapply import qapply from sympy.physics.quantum.qubit import Qubit, matrix_to_qubit +from sympy.physics.quantum.tensorproduct import matrix_tensor_product from pytket.circuit import Circuit, Op, OpType # gates that have an existing definition in sympy -_FIXED_GATE_MAP: Dict[OpType, Type[symgate.Gate]] = { +_FIXED_GATE_MAP: dict[OpType, type[symgate.Gate]] = { OpType.H: symgate.HadamardGate, OpType.S: symgate.PhaseGate, OpType.CX: symgate.CNotGate, @@ -53,10 +54,10 @@ OpType.Z: symgate.ZGate, } -ParamsType = List[Union[Expr, float]] +ParamsType = list[Expr | float] # Make sure the return matrix is Immutable https://github.com/sympy/sympy/issues/18733 SymGateFunc = Callable[[ParamsType], ImmutableMatrix] -SymGateMap = Dict[OpType, SymGateFunc] +SymGateMap = dict[OpType, SymGateFunc] # Begin matrix definitions for symbolic OpTypes # matches internal TKET definitions @@ -210,8 +211,7 @@ def symb_xxphase3(params: ParamsType) -> ImmutableMatrix: ) ) res3 = matrix_tensor_product(eye(2), xxphase2) - res = ImmutableMatrix(res1 * res2 * res3) - return res + return ImmutableMatrix(res1 * res2 * res3) def symb_phasedx(params: ParamsType) -> ImmutableMatrix: @@ -354,7 +354,7 @@ def is_registered(cls, typ: OpType) -> bool: return typ in cls._g_map -def _op_to_sympy_gate(op: Op, targets: List[int]) -> symgate.Gate: +def _op_to_sympy_gate(op: Op, targets: list[int]) -> symgate.Gate: # convert Op to sympy gate if op.type in _FIXED_GATE_MAP: return _FIXED_GATE_MAP[op.type](*targets) @@ -367,7 +367,7 @@ def _op_to_sympy_gate(op: Op, targets: List[int]) -> symgate.Gate: ) # pytket matrix basis indexing is in opposite order to sympy - targets = targets[::-1] + targets.reverse() if (not float_params) and SymGateRegister.is_registered(op.type): u_mat = SymGateRegister.get_func(op.type)(op.params) else: @@ -382,8 +382,7 @@ def _op_to_sympy_gate(op: Op, targets: List[int]) -> symgate.Gate: " Try registering your own symbolic matrix representation" " with SymGateRegister.func." ) from e - gate = symgate.UGate(targets, u_mat) - return gate + return symgate.UGate(targets, u_mat) def circuit_to_symbolic_gates(circ: Circuit) -> Mul: @@ -416,7 +415,7 @@ def circuit_to_symbolic_gates(circ: Circuit) -> Mul: for i in range(len(qubit_map)): outmat = symgate.IdentityGate(i) * outmat - return outmat * sympy.exp((circ.phase * sympy.pi * I)) + return outmat * sympy.exp(circ.phase * sympy.pi * I) def circuit_to_symbolic_unitary(circ: Circuit) -> ImmutableMatrix: @@ -462,7 +461,7 @@ def circuit_apply_symbolic_qubit(circ: Circuit, input_qb: Expr) -> Qubit: def circuit_apply_symbolic_statevector( - circ: Circuit, input_state: Optional[Union[np.ndarray, ImmutableMatrix]] = None + circ: Circuit, input_state: np.ndarray | ImmutableMatrix | None = None ) -> ImmutableMatrix: """Apply circuit to an optional input statevector to calculate output symbolic statevector. diff --git a/pytket/pytket/utils/term_sequence.py b/pytket/pytket/utils/term_sequence.py index fafd56d521..1b71d802aa 100644 --- a/pytket/pytket/utils/term_sequence.py +++ b/pytket/pytket/utils/term_sequence.py @@ -14,14 +14,15 @@ from typing import cast from pytket import Circuit -from pytket.circuit import PauliExpBox, CircBox +from pytket.circuit import CircBox, PauliExpBox from pytket.partition import ( - term_sequence, - PauliPartitionStrat, GraphColourMethod, + PauliPartitionStrat, + term_sequence, ) -from .operators import QubitPauliOperator + from .._tket.unit_id import UnitID +from .operators import QubitPauliOperator def gen_term_sequence_circuit( diff --git a/pytket/pytket/wasm/wasm.py b/pytket/pytket/wasm/wasm.py index bf9de1a538..e860712ef3 100644 --- a/pytket/pytket/wasm/wasm.py +++ b/pytket/pytket/wasm/wasm.py @@ -12,23 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -from os.path import exists import base64 import hashlib from functools import cached_property -from typing_extensions import deprecated +from os.path import exists from qwasm import ( # type: ignore - decode_module, - SEC_TYPE, - SEC_FUNCTION, - SEC_EXPORT, - LANG_TYPE_I32, - LANG_TYPE_I64, + LANG_TYPE_EMPTY, LANG_TYPE_F32, LANG_TYPE_F64, - LANG_TYPE_EMPTY, + LANG_TYPE_I32, + LANG_TYPE_I64, + SEC_EXPORT, + SEC_FUNCTION, + SEC_TYPE, + decode_module, ) +from typing_extensions import deprecated class WasmModuleHandler: @@ -125,9 +125,9 @@ def check(self) -> None: ] * entry.return_count else: raise ValueError( - f"Only parameter and return values of " - + f"i{self._int_size} types are" - + f" allowed, found type: {entry.return_type}" + "Only parameter and return values of " + f"i{self._int_size} types are" + f" allowed, found type: {entry.return_type}" ) elif entry.return_count == 1: function_signatures[idx]["return_types"] = [ diff --git a/pytket/pytket/zx/tensor_eval.py b/pytket/pytket/zx/tensor_eval.py index 39a9877399..dcf3f200bd 100644 --- a/pytket/pytket/zx/tensor_eval.py +++ b/pytket/pytket/zx/tensor_eval.py @@ -14,27 +14,29 @@ """Collection of methods to evaluate a ZXDiagram to a tensor. This uses the numpy tensor features, in particular the einsum evaluation and optimisations.""" -from typing import Dict, List, Any -from math import floor, pi, sqrt, cos, sin import warnings -import sympy +from math import cos, floor, pi, sin, sqrt +from typing import Any + import numpy as np +import sympy + from pytket.zx import ( - ZXDiagram, - ZXType, - ZXVert, - ZXGen, - PhasedGen, CliffordGen, DirectedGen, - ZXBox, + PhasedGen, QuantumType, Rewrite, + ZXBox, + ZXDiagram, + ZXGen, + ZXType, + ZXVert, ) try: import quimb.tensor as qtn # type: ignore -except ModuleNotFoundError as err: +except ModuleNotFoundError: warnings.warn( 'Missing package for tensor evaluation of ZX diagrams. Run "pip ' "install 'pytket[ZX]'\" to install the optional dependencies." @@ -44,14 +46,13 @@ def _gen_to_tensor(gen: ZXGen, rank: int) -> np.ndarray: if isinstance(gen, PhasedGen): return _spider_to_tensor(gen, rank) - elif isinstance(gen, CliffordGen): + if isinstance(gen, CliffordGen): return _clifford_to_tensor(gen, rank) - elif isinstance(gen, DirectedGen): + if isinstance(gen, DirectedGen): return _dir_gen_to_tensor(gen) - elif isinstance(gen, ZXBox): + if isinstance(gen, ZXBox): return _tensor_from_basic_diagram(gen.diagram) - else: - raise ValueError(f"Cannot convert generator of type {gen.type} to a tensor") + raise ValueError(f"Cannot convert generator of type {gen.type} to a tensor") def _spider_to_tensor(gen: PhasedGen, rank: int) -> np.ndarray: @@ -80,7 +81,7 @@ def _spider_to_tensor(gen: PhasedGen, rank: int) -> np.ndarray: t = np.full(size, 1.0, dtype=complex) constant = pow(sqrt(0.5), rank) for i in range(size): - parity = bin(i).count("1") + parity = (i).bit_count() t[i] += phase if parity % 2 == 0 else -phase t[i] *= constant elif gen.type == ZXType.Hbox: @@ -109,8 +110,7 @@ def _spider_to_tensor(gen: PhasedGen, rank: int) -> np.ndarray: raise ValueError( f"Cannot convert phased generator of type {gen.type} to a tensor" ) - t = t.reshape(tuple([2] * rank)) - return t + return t.reshape(tuple([2] * rank)) def _clifford_to_tensor(gen: CliffordGen, rank: int) -> np.ndarray: @@ -131,8 +131,7 @@ def _clifford_to_tensor(gen: CliffordGen, rank: int) -> np.ndarray: raise ValueError( f"Cannot convert Clifford generator of type {gen.type} to a tensor" ) - t = t.reshape(tuple([2] * rank)) - return t + return t.reshape(tuple([2] * rank)) def _dir_gen_to_tensor(gen: DirectedGen) -> np.ndarray: @@ -140,10 +139,9 @@ def _dir_gen_to_tensor(gen: DirectedGen) -> np.ndarray: t = np.ones((2, 2), dtype=complex) t[1, 0] = 0.0 return t - else: - raise ValueError( - f"Cannot convert directed generator of type {gen.type} to a tensor" - ) + raise ValueError( + f"Cannot convert directed generator of type {gen.type} to a tensor" + ) _id_tensor = np.asarray([[1, 0], [0, 1]], dtype=complex) @@ -162,7 +160,7 @@ def _tensor_from_basic_diagram(diag: ZXDiagram) -> np.ndarray: all_wires = diag.wires indices = dict(zip(all_wires, range(len(all_wires)))) next_index = len(all_wires) - tensor_list: List[Any] + tensor_list: list[Any] tensor_list = [] id_wires = set() res_indices = [] @@ -200,7 +198,7 @@ def _tensor_from_basic_diagram(diag: ZXDiagram) -> np.ndarray: net.full_simplify_(seq="ADCR") res_ten = net.contract(output_inds=res_indices, optimize="greedy") result: np.ndarray - if type(res_ten) == qtn.Tensor: + if isinstance(res_ten, qtn.Tensor): result = res_ten.data else: # Scalar @@ -284,7 +282,7 @@ def density_matrix_from_cptp_diagram(diag: ZXDiagram) -> np.ndarray: def fix_boundaries_to_binary_states( - diag: ZXDiagram, vals: Dict[ZXVert, int] + diag: ZXDiagram, vals: dict[ZXVert, int] ) -> ZXDiagram: new_diag = ZXDiagram(diag) b_lookup = dict(zip(diag.get_boundary(), new_diag.get_boundary())) @@ -308,7 +306,7 @@ def fix_boundaries_to_binary_states( return new_diag -def fix_inputs_to_binary_state(diag: ZXDiagram, vals: List[int]) -> ZXDiagram: +def fix_inputs_to_binary_state(diag: ZXDiagram, vals: list[int]) -> ZXDiagram: inputs = diag.get_boundary(type=ZXType.Input) if len(inputs) != len(vals): raise ValueError( @@ -318,7 +316,7 @@ def fix_inputs_to_binary_state(diag: ZXDiagram, vals: List[int]) -> ZXDiagram: return fix_boundaries_to_binary_states(diag, val_dict) -def fix_outputs_to_binary_state(diag: ZXDiagram, vals: List[int]) -> ZXDiagram: +def fix_outputs_to_binary_state(diag: ZXDiagram, vals: list[int]) -> ZXDiagram: outputs = diag.get_boundary(type=ZXType.Output) if len(outputs) != len(vals): raise ValueError( diff --git a/pytket/ruff.toml b/pytket/ruff.toml new file mode 100644 index 0000000000..de31804bfd --- /dev/null +++ b/pytket/ruff.toml @@ -0,0 +1,81 @@ +exclude = [ + "docs/pytket-docs-theming", + "pytket/_tket", + "pytket/_version.py", + "pytket/qasm/includes" +] + +target-version = "py310" + +lint.select = [ + "A", + "AIR", + # "ANN", + # "ARG", # TODO + "ASYNC", + # "B", + "BLE", + # "C", + # "C4", # TODO + # "C90", + # "COM", + # "CPY", + # "D", + "DJ", + # "DOC", + "DTZ", + "E", + # "EM", + # "ERA", # TODO + "EXE", + "F", + "FA", + # "FAST", + # "FBT", + # "FIX", + "FLY", + "FURB", + "G", + "I", + # "ICN", + # "INP", + "INT", + "ISC", + "LOG", + # "N", + # "NPY", # TODO + # "PD", + # "PERF", # TODO + # "PGH", + "PIE", + # "PL", + # "PT", + # "PTH", # TODO + # "PYI", # TODO + "Q", + "R", + "RET", + "RSE", + # "RUF", + # "S", + # "SIM", # TODO + # "SLF", + "SLOT", + "T10", + # "T20", + # "TCH", # TODO + # "TD", + # "TID", + # "TRY", + # "UP", # TODO + "W", + "YTT", +] + +lint.ignore = [ + "E501", # Allow long lines in strings + "E731", # OK to assign to lambdas + "E741", # Allow variable names like "l" + "F401", # Allow importing unused names in init files + "F403", # Allow wildcard imports in init files +] diff --git a/pytket/setup.py b/pytket/setup.py old mode 100755 new mode 100644 index 14f2000f02..0ed998f47e --- a/pytket/setup.py +++ b/pytket/setup.py @@ -12,15 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import multiprocessing import os -import subprocess -import json import shutil +import subprocess +from sysconfig import get_config_var + import setuptools # type: ignore -from setuptools import setup, Extension +from setuptools import Extension, setup from setuptools.command.build_ext import build_ext # type: ignore -from sysconfig import get_config_var from wheel.bdist_wheel import bdist_wheel as _bdist_wheel @@ -62,7 +63,9 @@ def run(self): os.mkdir(build_dir) install_dir = os.getenv("INSTALL_DIR") subprocess.run( - ["cmake", f"-DCMAKE_INSTALL_PREFIX={install_dir}", os.pardir], cwd=build_dir + ["cmake", f"-DCMAKE_INSTALL_PREFIX={install_dir}", os.pardir], + cwd=build_dir, + check=True, ) subprocess.run( [ @@ -72,8 +75,9 @@ def run(self): f"-j{os.getenv('PYTKET_CMAKE_N_THREADS', multiprocessing.cpu_count())}", ], cwd=build_dir, + check=True, ) - subprocess.run(["cmake", "--install", os.curdir], cwd=build_dir) + subprocess.run(["cmake", "--install", os.curdir], cwd=build_dir, check=True) lib_folder = os.path.join(install_dir, "lib") lib_names = ["libtklog.so", "libtket.so"] ext_suffix = get_config_var("EXT_SUFFIX") @@ -138,7 +142,6 @@ def run(self): shutil.rmtree(extdir) os.makedirs(extdir) - nix_ldflags = os.environ["NIX_LDFLAGS"].split() build_inputs = os.environ["propagatedBuildInputs"].split() binders = [f"{l}/lib" for l in build_inputs if "-binders" in l] @@ -149,7 +152,7 @@ def run(self): shutil.copy(libpath, extdir) for interface_file in os.listdir("pytket/_tket"): - if interface_file.endswith(".pyi") or interface_file.endswith(".py"): + if interface_file.endswith((".pyi", ".py")): shutil.copy(os.path.join("pytket/_tket", interface_file), extdir) @@ -159,10 +162,9 @@ def run(self): def get_build_ext(): if os.getenv("USE_NIX"): return NixBuild - elif os.getenv("NO_CONAN"): + if os.getenv("NO_CONAN"): return CMakeBuild - else: - return ConanBuild + return ConanBuild class bdist_wheel(_bdist_wheel): @@ -185,7 +187,7 @@ def finalize_options(self): "Tracker": "https://github.com/CQCL/tket/issues", }, description="Quantum computing toolkit and interface to the TKET compiler", - long_description=open("package.md", "r").read(), + long_description=open("package.md").read(), long_description_content_type="text/markdown", license="Apache 2", packages=setuptools.find_packages() + ["pytket.qasm.includes"], @@ -207,9 +209,7 @@ def finalize_options(self): "autoray >= 0.6.12", ], }, - ext_modules=[ - CMakeExtension("pytket._tket.{}".format(binder)) for binder in binders - ], + ext_modules=[CMakeExtension(f"pytket._tket.{binder}") for binder in binders], cmdclass={ "build_ext": get_build_ext(), "bdist_wheel": bdist_wheel, diff --git a/pytket/tests/add_circuit_test.py b/pytket/tests/add_circuit_test.py index 4eaafec4a3..e9e9623447 100644 --- a/pytket/tests/add_circuit_test.py +++ b/pytket/tests/add_circuit_test.py @@ -13,7 +13,6 @@ # limitations under the License. from pytket import Circuit, OpType -import pytest def gen_bell_state(reset_start: bool = False) -> Circuit: diff --git a/pytket/tests/ansatz_sequence_test.py b/pytket/tests/ansatz_sequence_test.py index d8dcdd46de..a703c098c0 100644 --- a/pytket/tests/ansatz_sequence_test.py +++ b/pytket/tests/ansatz_sequence_test.py @@ -12,15 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, Tuple, List +from typing import Dict, List, Tuple + import numpy as np -import pytest + from pytket import Circuit +from pytket.circuit import OpType, Qubit, fresh_symbol +from pytket.partition import GraphColourMethod, PauliPartitionStrat from pytket.pauli import Pauli, QubitPauliString -from pytket.circuit import fresh_symbol, OpType, Qubit -from pytket.utils import gen_term_sequence_circuit, QubitPauliOperator from pytket.transform import Transform -from pytket.partition import PauliPartitionStrat, GraphColourMethod +from pytket.utils import QubitPauliOperator, gen_term_sequence_circuit def test_basic_sequence() -> None: @@ -95,15 +96,15 @@ def test_nontrivial_sequence() -> None: } # We'll build the results up, and check at the end that they match exactly - calculated_results: Dict[ - PauliPartitionStrat, Dict[GraphColourMethod, Tuple[int, ...]] + calculated_results: dict[ + PauliPartitionStrat, dict[GraphColourMethod, tuple[int, ...]] ] = {} for strategy, colour_method_dict in expected_results.items(): calculated_results[strategy] = {} for colour_method in colour_method_dict: circ = gen_term_sequence_circuit(op, c, strategy, colour_method) - counts_list: List[int] = [circ.n_gates_of_type(OpType.CircBox)] + counts_list: list[int] = [circ.n_gates_of_type(OpType.CircBox)] circ_other = circ.copy() Transform.DecomposeBoxes().apply(circ_other) diff --git a/pytket/tests/architecture_aware_synthesis_test.py b/pytket/tests/architecture_aware_synthesis_test.py index 09f72df902..fd203272cd 100644 --- a/pytket/tests/architecture_aware_synthesis_test.py +++ b/pytket/tests/architecture_aware_synthesis_test.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pytket.circuit import Circuit, OpType from pytket.architecture import Architecture +from pytket.circuit import Circuit, OpType from pytket.passes import AASRouting, CNotSynthType, ComposePhasePolyBoxes from pytket.predicates import CompilationUnit diff --git a/pytket/tests/architecture_test.py b/pytket/tests/architecture_test.py index 3bb4b58816..37221c7f40 100644 --- a/pytket/tests/architecture_test.py +++ b/pytket/tests/architecture_test.py @@ -13,20 +13,22 @@ # limitations under the License. import json +from pathlib import Path + +from jsonschema import Draft7Validator # type: ignore from referencing import Registry from referencing.jsonschema import DRAFT7 -from jsonschema import Draft7Validator # type: ignore -from pathlib import Path + +from pytket.architecture import Architecture, FullyConnected, RingArch, SquareGrid from pytket.circuit import Node -from pytket.architecture import Architecture, SquareGrid, FullyConnected, RingArch curr_file_path = Path(__file__).resolve().parent schema_dir = curr_file_path.parent.parent / "schemas" -with open(schema_dir / "circuit_v1.json", "r") as f: +with open(schema_dir / "circuit_v1.json") as f: circ_schema = json.load(f) -with open(schema_dir / "architecture_v1.json", "r") as f: +with open(schema_dir / "architecture_v1.json") as f: arch_schema = json.load(f) -with open(schema_dir / "fullyconnected_v1.json", "r") as f: +with open(schema_dir / "fullyconnected_v1.json") as f: fc_schema = json.load(f) schema_store = [ @@ -92,9 +94,9 @@ def test_architecture_eq() -> None: assert arc != Architecture([(Node("s", i), Node("s", j)) for (i, j) in coupling]) # only Node IDs and coupling matters - g00, g01, g10, g11 = [ + g00, g01, g10, g11 = ( Node("gridNode", [i, j, 0]) for i in range(2) for j in range(2) - ] + ) sq_arc = Architecture([(g00, g01), (g01, g11), (g00, g10), (g10, g11)]) assert sq_arc == SquareGrid(2, 2) assert sq_arc != Architecture([(g00, g01), (g01, g11), (g00, g10)]) diff --git a/pytket/tests/assertion_test.py b/pytket/tests/assertion_test.py index 73a11a15b2..47bd678fe4 100644 --- a/pytket/tests/assertion_test.py +++ b/pytket/tests/assertion_test.py @@ -14,20 +14,18 @@ import numpy as np import pytest +from simulator import TketSimShotBackend # type: ignore from pytket.circuit import ( - ProjectorAssertionBox, - StabiliserAssertionBox, Circuit, + ProjectorAssertionBox, Qubit, + StabiliserAssertionBox, ) - from pytket.passes import ( DecomposeBoxes, ) - -from pytket.pauli import PauliStabiliser, Pauli -from simulator import TketSimShotBackend # type: ignore +from pytket.pauli import Pauli, PauliStabiliser def test_assertion_init() -> None: diff --git a/pytket/tests/backend_test.py b/pytket/tests/backend_test.py index 0bd16136d6..d4f663a31d 100644 --- a/pytket/tests/backend_test.py +++ b/pytket/tests/backend_test.py @@ -12,30 +12,28 @@ # See the License for the specific language governing permissions and # limitations under the License. -from collections import Counter - -from hypothesis import given, settings, strategies import json -import pytest +from collections import Counter from typing import Any, List import numpy as np +import pytest +from hypothesis import given, settings, strategies +from simulator import TketSimBackend, TketSimShotBackend # type: ignore +from strategies import backendresults, outcomearrays # type: ignore -from pytket.circuit import Circuit, OpType, BasisOrder, Qubit, Bit, Node -from pytket.predicates import CompilationUnit -from pytket.passes import PauliSimp, CliffordSimp -from pytket.mapping import MappingManager, LexiRouteRoutingMethod, LexiLabellingMethod from pytket.architecture import Architecture -from pytket.utils.outcomearray import OutcomeArray, readout_counts -from pytket.utils.prepare import prepare_circuit from pytket.backends.backend import Backend, ResultHandleTypeError -from pytket.backends.resulthandle import ResultHandle +from pytket.backends.backend_exceptions import CircuitNotRunError, InvalidResultType from pytket.backends.backendresult import BackendResult -from pytket.backends.backend_exceptions import InvalidResultType, CircuitNotRunError +from pytket.backends.resulthandle import ResultHandle from pytket.backends.status import CircuitStatus, StatusEnum - -from strategies import outcomearrays, backendresults # type: ignore -from simulator import TketSimShotBackend, TketSimBackend # type: ignore +from pytket.circuit import BasisOrder, Bit, Circuit, Node, OpType, Qubit +from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager +from pytket.passes import CliffordSimp, PauliSimp +from pytket.predicates import CompilationUnit +from pytket.utils.outcomearray import OutcomeArray, readout_counts +from pytket.utils.prepare import prepare_circuit def test_resulthandle() -> None: @@ -135,7 +133,7 @@ def test_swaps() -> None: def assert_single_entry_approx_value( - numbers: List[Any], + numbers: list[Any], index: int, value: float = 1.0, value_abs_eps: float = 1e-14, diff --git a/pytket/tests/backendinfo_test.py b/pytket/tests/backendinfo_test.py index 4f55289910..6308c30c77 100644 --- a/pytket/tests/backendinfo_test.py +++ b/pytket/tests/backendinfo_test.py @@ -19,14 +19,13 @@ from json import dumps, loads -from hypothesis import given, settings import pytest +import strategies as st # type: ignore +from hypothesis import given, settings +from pytket.architecture import FullyConnected, RingArch, SquareGrid from pytket.backends.backendinfo import BackendInfo, fully_connected_backendinfo -from pytket.architecture import SquareGrid, RingArch, FullyConnected -from pytket.circuit import OpType, Node - -import strategies as st # type: ignore +from pytket.circuit import Node, OpType def test_nodes() -> None: @@ -64,12 +63,12 @@ def test_gate_errors_options() -> None: bi = BackendInfo( "name", "device_name", "version", SquareGrid(3, 4), {OpType.CX, OpType.Rx} ) - assert bi.all_node_gate_errors == None - assert bi.all_edge_gate_errors == None - assert bi.all_readout_errors == None - assert bi.averaged_node_gate_errors == None - assert bi.averaged_edge_gate_errors == None - assert bi.averaged_readout_errors == None + assert bi.all_node_gate_errors is None + assert bi.all_edge_gate_errors is None + assert bi.all_readout_errors is None + assert bi.averaged_node_gate_errors is None + assert bi.averaged_edge_gate_errors is None + assert bi.averaged_readout_errors is None example_node_error = {Node(0): {OpType.H: 0.3, OpType.X: 0.4}} example_averaged_readout_errors = {Node(1): 0.4, Node(0): 0.3} @@ -177,7 +176,7 @@ def test_fullyconnected() -> None: "name", "device_name", "version", 10, {OpType.CX, OpType.Rx} ) assert bi.n_nodes == 10 - assert type(bi.architecture) == FullyConnected + assert isinstance(bi.architecture, FullyConnected) # https://github.com/CQCL/tket/issues/390 d = bi.to_dict() diff --git a/pytket/tests/boxes/phase_poly_box_test.py b/pytket/tests/boxes/phase_poly_box_test.py index 4a54cb2d3d..d371ae2996 100644 --- a/pytket/tests/boxes/phase_poly_box_test.py +++ b/pytket/tests/boxes/phase_poly_box_test.py @@ -2,27 +2,26 @@ import numpy as np -from pytket.circuit import Circuit, Qubit, PhasePolyBox +from pytket.circuit import Circuit, PhasePolyBox, Qubit +from pytket.circuit.named_types import PhasePolynomialDict, PhasePolynomialSequence from pytket.passes import ComposePhasePolyBoxes, DecomposeBoxes from pytket.utils import compare_unitaries -from pytket.circuit.named_types import PhasePolynomialDict, PhasePolynomialSequence def phase_polynomials_are_equal( - phase_poly_0: Union[PhasePolynomialDict, PhasePolynomialSequence], - phase_poly_1: Union[PhasePolynomialDict, PhasePolynomialSequence], + phase_poly_0: PhasePolynomialDict | PhasePolynomialSequence, + phase_poly_1: PhasePolynomialDict | PhasePolynomialSequence, ) -> bool: if isinstance(phase_poly_0, dict) and isinstance(phase_poly_1, dict): return phase_poly_0 == phase_poly_1 # If lists are given the dicts created from them should compare equal. # Lists of pairs can contain duplicate "keys", this ensures the last defined value # for a key is what is compared, replicating a dict definition with duplicate keys. - elif isinstance(phase_poly_0, list) and isinstance(phase_poly_1, list): + if isinstance(phase_poly_0, list) and isinstance(phase_poly_1, list): to_compare_0 = {tuple(pair[0]): pair[1] for pair in phase_poly_0} to_compare_1 = {tuple(pair[0]): pair[1] for pair in phase_poly_1} return to_compare_0 == to_compare_1 - else: - raise NotImplementedError("comparison not implemented") + raise NotImplementedError("comparison not implemented") def test_phase_polybox() -> None: @@ -69,8 +68,8 @@ def test_phase_polybox_II() -> None: c = Circuit(1, 1) n_qb = 1 qubit_indices = {Qubit(0): 0} - phase_polynomial: PhasePolynomialDict = {(True,): 0.1, (True,): 0.3} - phase_polynomial_alt: PhasePolynomialSequence = [([True], 0.1), ([True], 0.3)] + phase_polynomial: PhasePolynomialDict = {(True,): 0.3} + phase_polynomial_alt: PhasePolynomialSequence = [([True], 0.3)] linear_transformation = np.array([[1]]) p_box = PhasePolyBox(n_qb, qubit_indices, phase_polynomial, linear_transformation) p_box_alt = PhasePolyBox( @@ -90,9 +89,6 @@ def test_phase_polybox_II() -> None: assert p_box_ii.n_qubits == n_qb assert p_box.qubit_indices == qubit_indices assert p_box_ii.qubit_indices == qubit_indices - print(phase_polynomial) - print(p_box.phase_polynomial_as_list) - print(p_box.phase_polynomial) assert phase_polynomials_are_equal(p_box.phase_polynomial, phase_polynomial) assert phase_polynomials_are_equal( p_box.phase_polynomial_as_list, phase_polynomial_alt diff --git a/pytket/tests/characterisation_test.py b/pytket/tests/characterisation_test.py index a1c7267f85..08ae9b74b7 100644 --- a/pytket/tests/characterisation_test.py +++ b/pytket/tests/characterisation_test.py @@ -13,6 +13,7 @@ # limitations under the License. from pytket.circuit import Circuit, OpType, Qubit +from pytket.pauli import Pauli, QubitPauliString, QubitPauliTensor from pytket.tailoring import ( FrameRandomisation, PauliFrameRandomisation, @@ -20,9 +21,6 @@ apply_clifford_basis_change, apply_clifford_basis_change_tensor, ) -from pytket.pauli import Pauli, QubitPauliString, QubitPauliTensor - -import pytest def test_single_cycle_single_frame_randomisation() -> None: diff --git a/pytket/tests/circuit_test.py b/pytket/tests/circuit_test.py index bd63087cbf..861e043640 100644 --- a/pytket/tests/circuit_test.py +++ b/pytket/tests/circuit_test.py @@ -13,82 +13,77 @@ # limitations under the License. import json +import math +import pickle +from math import sqrt +from pathlib import Path +import numpy as np +import pytest +import strategies as st # type: ignore +from hypothesis import given, settings from jsonschema import validate # type: ignore -from pathlib import Path -import pickle +from scipy.linalg import block_diag +from sympy import Expr, Symbol, exp, pi, sympify from pytket.circuit import ( + Bit, + BitRegister, + CircBox, Circuit, - Op, - OpType, + ClassicalExpBox, Command, - fresh_symbol, - CircBox, - Unitary1qBox, - Unitary2qBox, - Unitary3qBox, - MultiplexorBox, - MultiplexedRotationBox, - MultiplexedU2Box, - MultiplexedTensoredU2Box, - StatePreparationBox, - DiagonalBox, ConjugationBox, + CustomGate, + CustomGateDef, + CXConfigType, + DiagonalBox, + DummyBox, ExpBox, + MultiplexedRotationBox, + MultiplexedTensoredU2Box, + MultiplexedU2Box, + MultiplexorBox, + Op, + OpType, PauliExpBox, - PauliExpPairBox, PauliExpCommutingSetBox, + PauliExpPairBox, QControlBox, - TermSequenceBox, - ToffoliBox, - ToffoliBoxSynthStrat, - CustomGateDef, - CustomGate, Qubit, - Bit, - BitRegister, QubitRegister, - CXConfigType, ResourceBounds, ResourceData, - DummyBox, - ClassicalExpBox, + StatePreparationBox, + TermSequenceBox, + ToffoliBox, + ToffoliBoxSynthStrat, + Unitary1qBox, + Unitary2qBox, + Unitary3qBox, + fresh_symbol, ) from pytket.circuit.display import get_circuit_renderer, render_circuit_as_html from pytket.circuit.named_types import ( BitstringToOpList, - BitstringToTensoredOpMap, - BitstringToTensoredOpList, BitstringToOpMap, + BitstringToTensoredOpList, + BitstringToTensoredOpMap, ParamType, PermutationMap, ) - -from pytket.pauli import Pauli from pytket.passes import ( CliffordSimp, - SynthesiseTket, DecomposeBoxes, RemoveRedundancies, + SynthesiseTket, ) -from pytket.transform import Transform, PauliSynthStrat - -import numpy as np -from scipy.linalg import block_diag -import sympy -from sympy import Symbol, pi, sympify, functions, Expr, exp -import math -from math import sqrt - -import pytest - -from hypothesis import given, settings -import strategies as st # type: ignore +from pytket.pauli import Pauli +from pytket.transform import PauliSynthStrat, Transform curr_file_path = Path(__file__).resolve().parent -with open(curr_file_path.parent.parent / "schemas/circuit_v1.json", "r") as f: +with open(curr_file_path.parent.parent / "schemas/circuit_v1.json") as f: schema = json.load(f) _0 = False @@ -788,11 +783,23 @@ def test_custom_gates() -> None: op0 = cmd0.op assert gate.type == op0.type assert gate.params == op0.params + c_d = c.dagger() + c_t = c.transpose() Transform.DecomposeBoxes().apply(c) coms = c.get_commands() assert str(coms[0]) == "CX q[0], q[3];" assert str(coms[1]) == "Rz(0.7) q[1];" assert str(coms[2]) == "CRz(1.3) q[0], q[1];" + Transform.DecomposeBoxes().apply(c_d) + coms_d = c_d.get_commands() + assert str(coms_d[0]) == "CRz(2.7) q[0], q[1];" + assert str(coms_d[1]) == "CX q[0], q[3];" + assert str(coms_d[2]) == "Rz(3.3) q[1];" + Transform.DecomposeBoxes().apply(c_t) + coms_t = c_t.get_commands() + assert str(coms_t[0]) == "CRz(1.3) q[0], q[1];" + assert str(coms_t[1]) == "CX q[0], q[3];" + assert str(coms_t[2]) == "Rz(0.7) q[1];" def test_errors() -> None: @@ -1002,7 +1009,7 @@ def test_commands_of_type() -> None: def test_empty_circuit() -> None: circ = Circuit(0) circt_dict = circ.to_dict() - assert type(circt_dict) == type({}) + assert isinstance(circt_dict, dict) assert len(circt_dict) > 0 assert Circuit(0) == Circuit(0) @@ -1133,13 +1140,13 @@ def test_clifford_checking() -> None: cx = c.get_commands()[1].op assert cx.is_clifford_type() t = c.get_commands()[2].op - assert t.is_clifford_type() == False + assert not t.is_clifford_type() rz1 = c.get_commands()[3].op - assert rz1.is_clifford_type() == False + assert not rz1.is_clifford_type() rz2 = c.get_commands()[4].op - assert rz2.is_clifford_type() == False + assert not rz2.is_clifford_type() m = c.get_commands()[5].op - assert m.is_clifford_type() == False + assert not m.is_clifford_type() def test_clifford_evaluation() -> None: @@ -1150,7 +1157,7 @@ def test_clifford_evaluation() -> None: iswap = c.get_commands()[1].op assert iswap.is_clifford() rz = c.get_commands()[2].op - assert rz.is_clifford() == False + assert not rz.is_clifford() def test_getting_registers() -> None: @@ -1265,6 +1272,22 @@ def test_counting_n_qubit_gates() -> None: assert c.n_nqb_gates(5) == 1 +def test_counting_conditional_gates() -> None: + c = Circuit(5, 2).X(0).H(1).Y(2).Z(3).S(4).CX(0, 1).CX(1, 2).CX(2, 3).CX(3, 4) + c.add_gate(OpType.H, [Qubit(0)], condition=Bit(0)) + c.add_gate(OpType.H, [Qubit(1)], condition=(Bit(0) & Bit(1))) + c.add_gate(OpType.CX, [Qubit(0), Qubit(1)], condition=Bit(1)) + assert c.n_gates_of_type(OpType.H, include_conditional=True) == 3 + assert c.n_gates_of_type(OpType.H, include_conditional=False) == 1 + assert c.n_gates_of_type(OpType.H) == 1 + assert c.n_gates_of_type(OpType.X, include_conditional=True) == 1 + assert c.n_gates_of_type(OpType.X, include_conditional=False) == 1 + assert c.n_gates_of_type(OpType.X) == 1 + assert c.n_gates_of_type(OpType.CX, include_conditional=True) == 5 + assert c.n_gates_of_type(OpType.CX, include_conditional=False) == 4 + assert c.n_gates_of_type(OpType.CX) == 4 + + def test_qcontrol_box_constructors() -> None: # only one argument qcbox1 = QControlBox(Op.create(OpType.S)) @@ -1423,8 +1446,8 @@ def test_add_circbox_with_registers() -> None: c0.CCX(breg[0], breg[1], breg[2]) cbox = CircBox(c0) c = Circuit() - xreg = c.add_q_register("x", 3) - yreg = c.add_q_register("y", 2) + c.add_q_register("x", 3) + c.add_q_register("y", 2) zreg = c.add_q_register("z", 3) wreg = c.add_q_register("w", 2) for qb in c.qubits: @@ -1442,16 +1465,16 @@ def test_add_circbox_with_registers() -> None: def test_add_circbox_with_mixed_registers() -> None: c0 = Circuit() - c0_qreg1 = c0.add_q_register("q1", 2) - c0_qreg2 = c0.add_q_register("q2", 3) - c0_creg1 = c0.add_c_register("c1", 4) - c0_creg2 = c0.add_c_register("c2", 5) + c0.add_q_register("q1", 2) + c0.add_q_register("q2", 3) + c0.add_c_register("c1", 4) + c0.add_c_register("c2", 5) cbox = CircBox(c0) c = Circuit() - c_qreg1 = c.add_q_register("q1", 2) - c_qreg2 = c.add_q_register("q2", 3) - c_creg1 = c.add_c_register("c1", 4) - c_creg2 = c.add_c_register("c2", 5) + c.add_q_register("q1", 2) + c.add_q_register("q2", 3) + c.add_c_register("c1", 4) + c.add_c_register("c2", 5) c.add_circbox_with_regmap( cbox, qregmap={"q1": "q1", "q2": "q2"}, cregmap={"c1": "c1", "c2": "c2"} @@ -1537,7 +1560,7 @@ def test_bad_circbox() -> None: b = circ.add_c_register("b", 5) c = circ.add_c_register("c", 5) circ.add_classicalexpbox_register(a | b, c.to_list()) - with pytest.raises(RuntimeError) as e: + with pytest.raises(RuntimeError): _ = CircBox(circ) diff --git a/pytket/tests/classical_test.py b/pytket/tests/classical_test.py index e365074270..8364e11115 100644 --- a/pytket/tests/classical_test.py +++ b/pytket/tests/classical_test.py @@ -12,40 +12,42 @@ # See the License for the specific language governing permissions and # limitations under the License. -import operator -from typing import Callable, Dict, List, Tuple, Union, TypeVar import json +import operator +from collections.abc import Callable from pathlib import Path +from typing import Dict, List, Tuple, TypeVar, Union -from jsonschema import validate # type: ignore +import pytest from hypothesis import given, settings, strategies from hypothesis.strategies import SearchStrategy +from jsonschema import validate # type: ignore +from strategies import binary_digits, reg_name_regex, uint32, uint64 # type: ignore +from sympy import Symbol from pytket import wasm - -import pytest from pytket._tket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE from pytket.circuit import ( - BitRegister, - QubitRegister, Bit, - UnitID, + BitRegister, Circuit, - OpType, - Qubit, + ClassicalExpBox, Conditional, - Op, - SetBitsOp, MultiBitOp, + Op, + OpType, + Qubit, + QubitRegister, RangePredicateOp, - ClassicalExpBox, + SetBitsOp, ) from pytket.circuit.logic_exp import ( BinaryOp, BitLogicExp, BitWiseOp, - PredicateExp, LogicExp, + NullaryOp, + PredicateExp, RegEq, RegGeq, RegGt, @@ -56,29 +58,23 @@ RegPow, RegWiseOp, UnaryOp, - NullaryOp, + create_bit_logic_exp, + create_reg_logic_exp, + if_bit, + if_not_bit, reg_eq, reg_geq, reg_gt, reg_leq, reg_lt, reg_neq, - if_bit, - if_not_bit, - create_bit_logic_exp, - create_reg_logic_exp, ) from pytket.circuit.named_types import RenameUnitsMap - from pytket.passes import DecomposeClassicalExp, FlattenRegisters -from sympy import Symbol - -from strategies import reg_name_regex, binary_digits, uint32, uint64 # type: ignore - curr_file_path = Path(__file__).resolve().parent -with open(curr_file_path.parent.parent / "schemas/circuit_v1.json", "r") as f: +with open(curr_file_path.parent.parent / "schemas/circuit_v1.json") as f: schema = json.load(f) @@ -358,10 +354,10 @@ def test_wasm_12() -> None: def test_wasm_handler() -> None: - w = wasm.WasmFileHandler("testfile.wasm") + wasm.WasmFileHandler("testfile.wasm") with pytest.raises(ValueError): - w2 = wasm.WasmFileHandler("notexistingfile.wasm") + wasm.WasmFileHandler("notexistingfile.wasm") def test_wasm_function_check() -> None: @@ -452,7 +448,7 @@ def test_wasm_function_check_8() -> None: c = Circuit(20, 20) c0 = c.add_c_register("c0", 32) c1 = c.add_c_register("c1", 4) - c2 = c.add_c_register("c2", 5) + c.add_c_register("c2", 5) c.add_wasm_to_reg("add_something", w, [c0], [c1]) assert c.depth() == 1 @@ -463,7 +459,7 @@ def test_wasm_function_check_9() -> None: c = Circuit(20, 20) c0 = c.add_c_register("c0", 53) c1 = c.add_c_register("c1", 4) - c2 = c.add_c_register("c2", 5) + c.add_c_register("c2", 5) with pytest.raises(ValueError): c.add_wasm_to_reg("add_something", w, [c0], [c1]) @@ -487,7 +483,7 @@ def test_add_wasm_to_reg() -> None: def test_wasmfilehandler_without_init() -> None: with pytest.raises(ValueError): - w = wasm.WasmFileHandler("testfile-without-init.wasm") + _ = wasm.WasmFileHandler("testfile-without-init.wasm") def test_wasmfilehandler_without_init_no_check() -> None: @@ -507,34 +503,34 @@ def test_wasmfilehandler_without_init_no_check() -> None: def test_wasmfilehandler_invalid_file_1_c_32() -> None: with pytest.raises(ValueError): - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-1-emcc.wasm", int_size=32 ) def test_wasmfilehandler_invalid_file_1_c_64() -> None: with pytest.raises(ValueError): - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-1-emcc.wasm", int_size=64 ) def test_wasmfilehandler_invalid_file_1_e_32() -> None: with pytest.raises(ValueError): - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-2-emcc.wasm", int_size=32 ) def test_wasmfilehandler_invalid_file_1_e_64() -> None: with pytest.raises(ValueError): - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-2-emcc.wasm", int_size=64 ) def test_wasmfilehandler_invalid_file_1_c_32_no_check() -> None: - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-1-emcc.wasm", int_size=32, check_file=False, @@ -542,7 +538,7 @@ def test_wasmfilehandler_invalid_file_1_c_32_no_check() -> None: def test_wasmfilehandler_invalid_file_1_c_64_no_check() -> None: - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-1-emcc.wasm", int_size=64, check_file=False, @@ -550,7 +546,7 @@ def test_wasmfilehandler_invalid_file_1_c_64_no_check() -> None: def test_wasmfilehandler_invalid_file_1_e_32_no_check() -> None: - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-2-emcc.wasm", int_size=32, check_file=False, @@ -558,7 +554,7 @@ def test_wasmfilehandler_invalid_file_1_e_32_no_check() -> None: def test_wasmfilehandler_invalid_file_1_e_64_no_check() -> None: - w = wasm.WasmFileHandler( + _ = wasm.WasmFileHandler( "wasm-generation/wasmfromcpp/invalid-with-print-2-emcc.wasm", int_size=64, check_file=False, @@ -592,7 +588,7 @@ def test_wasmfilehandler_repr() -> None: function 'no_parameters' with 0 i32 parameter(s) and 1 i32 return value(s) function 'new_function' with 0 i32 parameter(s) and 1 i32 return value(s) unsupported function with invalid parameter or result type: 'add_something' -""" +""" # noqa: W291 ) @@ -610,7 +606,7 @@ def test_wasmfilehandler_repr_64() -> None: unsupported function with invalid parameter or result type: 'no_return' unsupported function with invalid parameter or result type: 'no_parameters' unsupported function with invalid parameter or result type: 'new_function' -""" +""" # noqa: W291 ) @@ -633,7 +629,7 @@ def test_wasmfilehandler_repr_2() -> None: function 'mixed_up_3' with 3 i32 parameter(s) and 1 i32 return value(s) function 'unse_internal' with 1 i32 parameter(s) and 1 i32 return value(s) unsupported function with invalid parameter or result type: 'add_something' -""" +""" # noqa: W291 ) @@ -656,7 +652,7 @@ def test_wasmfilehandler_repr_64_2() -> None: unsupported function with invalid parameter or result type: 'mixed_up_2' unsupported function with invalid parameter or result type: 'mixed_up_3' unsupported function with invalid parameter or result type: 'unse_internal' -""" +""" # noqa: W291 ) @@ -684,7 +680,7 @@ def test_wasmfilehandler_multivalue_clang() -> None: function '__wasm_call_ctors' with 0 i32 parameter(s) and 0 i32 return value(s) function 'init' with 0 i32 parameter(s) and 0 i32 return value(s) unsupported function with invalid parameter or result type: 'divmod' -""" +""" # noqa: W291 ) @@ -767,7 +763,7 @@ def qubit_register( reg=strategies.one_of(bit_register(), qubit_register()), index=strategies.integers(min_value=0, max_value=32), ) -def test_registers(reg: Union[BitRegister, QubitRegister], index: int) -> None: +def test_registers(reg: BitRegister | QubitRegister, index: int) -> None: unit_type = Qubit if type(reg) is QubitRegister else Bit if index < reg.size: assert reg[index] == unit_type(reg.name, index) @@ -819,11 +815,11 @@ def wrapper(*args: int) -> int: bit_exp=primitive_bit_logic_exps(), constants=strategies.tuples(binary_digits, binary_digits), ) -def test_bit_exp(bit_exp: BitLogicExp, constants: Tuple[int, int]) -> None: +def test_bit_exp(bit_exp: BitLogicExp, constants: tuple[int, int]) -> None: iter_c = iter(constants) for inp in bit_exp.all_inputs(): bit_exp.set_value(inp, next(iter_c)) - op_map: Dict[BitWiseOp, Callable] = { + op_map: dict[BitWiseOp, Callable] = { BitWiseOp.AND: operator.and_, BitWiseOp.OR: operator.or_, BitWiseOp.XOR: operator.xor, @@ -856,7 +852,7 @@ def primitive_reg_logic_exps( op = draw(ops) exp_type = LogicExp.factory(op) - args: List[BitRegister] = [draw(bit_regs)] + args: list[BitRegister] = [draw(bit_regs)] if issubclass(exp_type, BinaryOp): if issubclass( exp_type, @@ -887,7 +883,7 @@ def primitive_reg_logic_exps( uint64, ), ) -def test_reg_exp(reg_exp: RegLogicExp, constants: Tuple[int, int]) -> None: +def test_reg_exp(reg_exp: RegLogicExp, constants: tuple[int, int]) -> None: if isinstance(reg_exp, RegPow): # to stop massive numbers constants = (min(1000, constants[0]), min(constants[1], 3)) @@ -895,7 +891,7 @@ def test_reg_exp(reg_exp: RegLogicExp, constants: Tuple[int, int]) -> None: for inp in reg_exp.all_inputs(): reg_exp.set_value(inp, next(iter_c)) - op_map: Dict[RegWiseOp, Callable] = { + op_map: dict[RegWiseOp, Callable] = { RegWiseOp.AND: operator.and_, RegWiseOp.OR: operator.or_, RegWiseOp.XOR: operator.xor, @@ -993,7 +989,7 @@ def bit_const_predicates( draw: DrawType, exp: SearchStrategy[BitLogicExp] = composite_bit_logic_exps(), operators: SearchStrategy[ - Callable[[Union[Bit, BitLogicExp]], PredicateExp] + Callable[[Bit | BitLogicExp], PredicateExp] ] = strategies.sampled_from([if_bit, if_not_bit]), ) -> PredicateExp: func = draw(operators) @@ -1006,7 +1002,7 @@ def reg_const_predicates( draw: DrawType, exp: SearchStrategy[RegLogicExp] = composite_reg_logic_exps(), operators: SearchStrategy[ - Callable[[Union[RegLogicExp, BitRegister], int], PredicateExp] + Callable[[RegLogicExp | BitRegister, int], PredicateExp] ] = strategies.sampled_from([reg_eq, reg_neq, reg_lt, reg_gt, reg_leq, reg_geq]), constants: SearchStrategy[int] = uint64, ) -> PredicateExp: @@ -1037,7 +1033,7 @@ def test_regpredicate(condition: PredicateExp) -> None: circ.add_bit(inp, reject_dups=False) circ.X(qb, condition=condition) - assert circ.n_gates_of_type(OpType.ClassicalExpBox) == 1 + assert circ.n_gates_of_type(OpType.ClassicalExpBox) == 0 newcirc = circ.copy() DecomposeClassicalExp().apply(newcirc) @@ -1115,178 +1111,6 @@ def check_serialization_roundtrip(circ: Circuit) -> None: assert circ_from_dict.to_dict() == circ_dict -def test_decomposition_known() -> None: - bits = [Bit(i) for i in range(10)] - registers = [BitRegister(c, 3) for c in "abdefghijk"] - - qreg = QubitRegister("q_", 10) - circ = Circuit() - conditioned_circ = Circuit() - decomposed_circ = Circuit() - - for c in (circ, conditioned_circ, decomposed_circ): - for b in bits: - c.add_bit(b) - for br in registers: - for b in br.to_list(): - c.add_bit(b, reject_dups=False) - c.add_q_register(qreg.name, qreg.size) - - circ.H(qreg[0], condition=bits[0]) - circ.X(qreg[0], condition=if_bit(bits[1])) - circ.S(qreg[0]) - circ.T(qreg[1], condition=if_not_bit(bits[2])) - circ.Z(qreg[0], condition=(bits[2] & bits[3])) - circ.Z(qreg[1], condition=if_not_bit(bits[3] & bits[4])) - big_exp = bits[4] | bits[5] ^ bits[6] | bits[7] & bits[8] - # ^ no need for parantheses as python operator precedence - # will enforce correct precedence in LogicExp - circ.CX(qreg[0], qreg[1]) - circ.CX(qreg[1], qreg[2], condition=big_exp) - - circ.add_barrier(qreg.to_list()) - - circ.H(qreg[2], condition=reg_eq(registers[0], 3)) - circ.X(qreg[3], condition=reg_lt(registers[1], 6)) - circ.Y(qreg[4], condition=reg_neq(registers[2], 5)) - circ.Z(qreg[5], condition=reg_gt(registers[3], 3)) - circ.S(qreg[6], condition=reg_leq(registers[4], 6)) - circ.T(qreg[7], condition=reg_geq(registers[5], 3)) - big_reg_exp = registers[4] & registers[3] | registers[6] ^ registers[7] - circ.CX(qreg[3], qreg[4], condition=reg_eq(big_reg_exp, 3)) - - circ.add_classicalexpbox_bit( - bits[4] | bits[5] & bits[3], [bits[0]], condition=bits[1] - ) - check_serialization_roundtrip(circ) - - temp_bits = BitRegister(_TEMP_BIT_NAME, 64) - - def temp_reg(i: int) -> BitRegister: - return BitRegister(f"{_TEMP_BIT_REG_BASE}_{i}", 64) - - for b in (temp_bits[i] for i in range(0, 10)): - conditioned_circ.add_bit(b) - - for t_r in (temp_reg(i) for i in range(0, 1)): - conditioned_circ.add_c_register(t_r.name, t_r.size) - - # relies on existing interface for adding conditionals - # may need a more low level interface for that if we decide to get rid of it - conditioned_circ.H(qreg[0], condition_bits=[bits[0]], condition_value=1) - conditioned_circ.X(qreg[0], condition_bits=[bits[1]], condition_value=1) - conditioned_circ.S(qreg[0]) - conditioned_circ.T(qreg[1], condition_bits=[bits[2]], condition_value=0) - - conditioned_circ.add_classicalexpbox_bit((bits[2] & bits[3]), [temp_bits[0]]) - conditioned_circ.Z(qreg[0], condition_bits=[temp_bits[0]], condition_value=1) - conditioned_circ.add_classicalexpbox_bit((bits[3] & bits[4]), [temp_bits[1]]) - conditioned_circ.Z(qreg[1], condition_bits=[temp_bits[1]], condition_value=0) - conditioned_circ.CX(qreg[0], qreg[1]) - conditioned_circ.add_classicalexpbox_bit(big_exp, [temp_bits[2]]) - conditioned_circ.CX( - qreg[1], qreg[2], condition_bits=[temp_bits[2]], condition_value=1 - ) - - conditioned_circ.add_barrier(qreg.to_list()) - - registers_lists = [reg.to_list() for reg in registers] - - conditioned_circ.add_c_range_predicate(3, 3, registers_lists[0], temp_bits[3]) - conditioned_circ.H(qreg[2], condition_bits=[temp_bits[3]], condition_value=1) - conditioned_circ.add_c_range_predicate(0, 5, registers_lists[1], temp_bits[4]) - conditioned_circ.X(qreg[3], condition_bits=[temp_bits[4]], condition_value=1) - conditioned_circ.add_c_range_predicate(5, 5, registers_lists[2], temp_bits[5]) - conditioned_circ.Y(qreg[4], condition_bits=[temp_bits[5]], condition_value=0) - conditioned_circ.add_c_range_predicate( - 4, 18446744073709551615, registers_lists[3], temp_bits[6] - ) - conditioned_circ.Z(qreg[5], condition_bits=[temp_bits[6]], condition_value=1) - conditioned_circ.add_c_range_predicate(0, 6, registers_lists[4], temp_bits[7]) - conditioned_circ.S(qreg[6], condition_bits=[temp_bits[7]], condition_value=1) - conditioned_circ.add_c_range_predicate( - 3, 18446744073709551615, registers_lists[5], temp_bits[8] - ) - conditioned_circ.T(qreg[7], condition_bits=[temp_bits[8]], condition_value=1) - - temp_reg_bits = [temp_reg(0)[i] for i in range(3)] - conditioned_circ.add_classicalexpbox_register(big_reg_exp, temp_reg_bits) - conditioned_circ.add_c_range_predicate(3, 3, temp_reg_bits, temp_bits[9]) - conditioned_circ.CX( - qreg[3], qreg[4], condition_bits=[temp_bits[9]], condition_value=1 - ) - conditioned_circ.add_classicalexpbox_bit( - bits[4] | bits[5] & bits[3], [bits[0]], condition=bits[1] - ) - - assert compare_commands_box(circ, conditioned_circ) - - for b in (temp_bits[i] for i in range(0, 12)): - decomposed_circ.add_bit(b) - - decomposed_circ.add_c_register(BitRegister(f"{_TEMP_BIT_REG_BASE}_0", 3)) - decomposed_circ.add_c_register(BitRegister(f"{_TEMP_BIT_REG_BASE}_1", 64)) - decomposed_circ.add_c_register(BitRegister(f"{_TEMP_BIT_REG_BASE}_2", 64)) - - decomposed_circ.H(qreg[0], condition_bits=[bits[0]], condition_value=1) - decomposed_circ.X(qreg[0], condition_bits=[bits[1]], condition_value=1) - decomposed_circ.S(qreg[0]) - decomposed_circ.T(qreg[1], condition_bits=[bits[2]], condition_value=0) - decomposed_circ.add_c_and(bits[2], bits[3], temp_bits[0]) - decomposed_circ.Z(qreg[0], condition_bits=[temp_bits[0]], condition_value=1) - decomposed_circ.add_c_and(bits[3], bits[4], temp_bits[1]) - decomposed_circ.Z(qreg[1], condition_bits=[temp_bits[1]], condition_value=0) - decomposed_circ.CX(qreg[0], qreg[1]) - decomposed_circ.add_c_range_predicate(3, 3, registers_lists[0], temp_bits[3]) - decomposed_circ.add_c_range_predicate(0, 5, registers_lists[1], temp_bits[4]) - decomposed_circ.add_c_range_predicate(5, 5, registers_lists[2], temp_bits[5]) - decomposed_circ.add_c_range_predicate( - 4, 18446744073709551615, registers_lists[3], temp_bits[6] - ) - decomposed_circ.add_c_range_predicate(0, 6, registers_lists[4], temp_bits[7]) - decomposed_circ.add_c_range_predicate( - 3, 18446744073709551615, registers_lists[5], temp_bits[8] - ) - - decomposed_circ.add_c_xor(bits[5], bits[6], temp_bits[10]) - decomposed_circ.add_c_and(bits[7], bits[8], temp_bits[11]) - decomposed_circ.add_c_or(bits[4], temp_bits[10], temp_bits[10]) - decomposed_circ.add_c_or(temp_bits[10], temp_bits[11], temp_bits[2]) - decomposed_circ.CX( - qreg[1], qreg[2], condition_bits=[temp_bits[2]], condition_value=1 - ) - - decomposed_circ.add_barrier(qreg.to_list()) - - decomposed_circ.H(qreg[2], condition_bits=[temp_bits[3]], condition_value=1) - decomposed_circ.X(qreg[3], condition_bits=[temp_bits[4]], condition_value=1) - decomposed_circ.Y(qreg[4], condition_bits=[temp_bits[5]], condition_value=0) - decomposed_circ.Z(qreg[5], condition_bits=[temp_bits[6]], condition_value=1) - decomposed_circ.S(qreg[6], condition_bits=[temp_bits[7]], condition_value=1) - decomposed_circ.T(qreg[7], condition_bits=[temp_bits[8]], condition_value=1) - - decomposed_circ.add_c_and_to_registers(registers[4], registers[3], temp_reg(1)) - decomposed_circ.add_c_xor_to_registers(registers[6], registers[7], temp_reg(2)) - decomposed_circ.add_c_or_to_registers( - temp_reg(1), BitRegister(temp_reg(2).name, 3), temp_reg(0) - ) - decomposed_circ.add_c_range_predicate(3, 3, temp_reg(0).to_list()[:3], temp_bits[9]) - decomposed_circ.CX( - qreg[3], qreg[4], condition_bits=[temp_bits[9]], condition_value=1 - ) - decomposed_circ.add_c_and( - bits[5], bits[3], temp_bits[10], condition_bits=[bits[1]], condition_value=1 - ) - decomposed_circ.add_c_or( - bits[4], temp_bits[10], bits[0], condition_bits=[bits[1]], condition_value=1 - ) - check_serialization_roundtrip(decomposed_circ) - circ_copy = circ.copy() - - DecomposeClassicalExp().apply(circ_copy) - assert circ_copy == decomposed_circ - - def test_conditional() -> None: c = Circuit(1, 2) c.H(0, condition_bits=[0, 1], condition_value=3) @@ -1448,7 +1272,7 @@ def test_renaming() -> None: c = circ.add_c_register("c", 3) circ.add_classicalexpbox_bit(a[0] & b[0] | c[0], [a[0]]) circ.add_classicalexpbox_bit(a[0] & c[2], [c[0]]) - d = [Bit("d", index) for index in range(0, 3)] + d = [Bit("d", index) for index in range(3)] bmap: RenameUnitsMap = {a[0]: d[0], b[0]: d[1], c[0]: d[2]} original_commands = circ.get_commands() assert circ.rename_units(bmap) diff --git a/pytket/tests/clexpr_test.py b/pytket/tests/clexpr_test.py index e078224aa0..a9e81bca21 100644 --- a/pytket/tests/clexpr_test.py +++ b/pytket/tests/clexpr_test.py @@ -12,6 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json +from pathlib import Path + +from jsonschema import validate # type: ignore + from pytket.circuit import ( Bit, CircBox, @@ -21,10 +26,14 @@ ClExprOp, ClOp, ClRegVar, - OpType, WiredClExpr, ) -from pytket.qasm import circuit_to_qasm_str, circuit_from_qasm_str +from pytket.qasm import circuit_from_qasm_str, circuit_to_qasm_str + +with open( + Path(__file__).resolve().parent.parent.parent / "schemas/circuit_v1.json" +) as f: + SCHEMA = json.load(f) def test_op() -> None: @@ -114,16 +123,25 @@ def test_wexpr() -> None: def test_adding_to_circuit() -> None: - expr = ClExpr(op=ClOp.BitXor, args=[ClBitVar(0), ClBitVar(1)]) - wexpr = WiredClExpr(expr=expr, bit_posn={0: 0, 1: 1}, output_posn=[2]) + expr0 = ClExpr(op=ClOp.BitXor, args=[ClBitVar(0), ClBitVar(1)]) + wexpr0 = WiredClExpr(expr=expr0, bit_posn={0: 0, 1: 1}, output_posn=[2]) + expr1 = ClExpr( + op=ClOp.RegDiv, + args=[ClRegVar(0), ClExpr(op=ClOp.RegAdd, args=[2, ClBitVar(0)])], + ) + wexpr1 = WiredClExpr( + expr=expr1, bit_posn={0: 1}, reg_posn={0: [2, 0]}, output_posn=[2, 0] + ) c = Circuit(0, 3) - c.add_clexpr(wexpr, c.bits) + c.add_clexpr(wexpr0, c.bits) + c.add_clexpr(wexpr1, c.bits) cmds = c.get_commands() - assert len(cmds) == 1 + assert len(cmds) == 2 op = cmds[0].op assert isinstance(op, ClExprOp) - assert op.expr == wexpr + assert op.expr == wexpr0 d = c.to_dict() + validate(instance=d, schema=SCHEMA) c1 = Circuit.from_dict(d) assert c == c1 d1 = c1.to_dict() @@ -217,3 +235,23 @@ def test_circbox() -> None: c2 = c1.copy() c2.flatten_registers() assert c1 == c2 + + +def test_add_logicexp_as_clexpr() -> None: + c = Circuit() + a_reg = c.add_c_register("a", 3) + b_reg = c.add_c_register("b", 3) + c_reg = c.add_c_register("c", 3) + c.add_clexpr_from_logicexp(a_reg | b_reg, c_reg.to_list()) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +creg a[3]; +creg b[3]; +creg c[3]; +c = (a | b); +""" + ) diff --git a/pytket/tests/compilation_test.py b/pytket/tests/compilation_test.py index fd87e95f2b..c5a22beeb3 100644 --- a/pytket/tests/compilation_test.py +++ b/pytket/tests/compilation_test.py @@ -12,17 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + +from pytket.architecture import Architecture from pytket.circuit import Circuit, OpType, reg_eq from pytket.passes import ( + CXMappingPass, FullPeepholeOptimise, PassSelector, - CXMappingPass, scratch_reg_resize_pass, ) -from pytket.architecture import Architecture from pytket.placement import Placement from pytket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE -import pytest def test_compilation() -> None: @@ -182,9 +183,6 @@ def circ_depth(circ: Circuit) -> int: def test_PassSelector_wrong_pass() -> None: - fp = FullPeepholeOptimise() - fp2 = FullPeepholeOptimise(allow_swaps=False) - arc = Architecture([(1, 2)]) pl = Placement(arc) @@ -200,7 +198,7 @@ def circ_depth(circ: Circuit) -> int: circ = Circuit(3).H(1).H(0).H(1).H(0).X(1).CX(1, 0).CX(0, 1).CX(1, 2) with pytest.raises(Exception): - result = sp.apply(circ) + _ = sp.apply(circ) def test_PassSelector_empty_pass() -> None: @@ -208,7 +206,7 @@ def circ_depth(circ: Circuit) -> int: return circ.depth() with pytest.raises(Exception): - sp = PassSelector([], circ_depth) + _ = PassSelector([], circ_depth) def test_PassSelector_ii() -> None: @@ -309,7 +307,7 @@ def test_resize_scratch_registers() -> None: reg_a = circ.add_c_register("a", 1) reg_b = circ.add_c_register("b", 1) circ.X(0, condition=reg_eq(reg_a ^ reg_b, 1)) - assert circ.get_c_register(f"{_TEMP_BIT_REG_BASE}_0").size == 64 + assert circ.get_c_register(f"{_TEMP_BIT_REG_BASE}_0").size == 1 c_compiled = circ.copy() scratch_reg_resize_pass(10).apply(c_compiled) assert circ == c_compiled diff --git a/pytket/tests/distribution_test.py b/pytket/tests/distribution_test.py index 22a6240ae8..ec370b7503 100644 --- a/pytket/tests/distribution_test.py +++ b/pytket/tests/distribution_test.py @@ -13,13 +13,15 @@ # limitations under the License. from collections import Counter + +import pytest from numpy import isclose + from pytket.utils import ( - ProbabilityDistribution, EmpiricalDistribution, + ProbabilityDistribution, convex_combination, ) -import pytest def test_probability_distribution() -> None: @@ -34,7 +36,7 @@ def test_probability_distribution() -> None: assert pd1["c"] == 0.0 assert pd == pd1 with pytest.warns(UserWarning): - pd2 = ProbabilityDistribution({"a": 2 / 3, "b": 4 / 3}) + _ = ProbabilityDistribution({"a": 2 / 3, "b": 4 / 3}) pd3 = ProbabilityDistribution({"a": 2 / 9, "b": 4 / 9, "c": 1 / 3}) pd4 = convex_combination([(pd, 1 / 3), (pd3, 2 / 3)]) assert pd4 == ProbabilityDistribution({"a": 7 / 27, "b": 14 / 27, "c": 2 / 9}) diff --git a/pytket/tests/logic_exp_test.py b/pytket/tests/logic_exp_test.py index fe7648ceda..2e3c16b97f 100644 --- a/pytket/tests/logic_exp_test.py +++ b/pytket/tests/logic_exp_test.py @@ -1,18 +1,18 @@ from pytket._tket.unit_id import BitRegister from pytket.circuit.logic_exp import ( - RegOr, - RegAnd, - BitOr, BitAnd, + BitOr, BitXor, - RegXor, RegAdd, - RegSub, - RegMul, + RegAnd, RegDiv, - RegPow, RegLsh, + RegMul, + RegOr, + RegPow, RegRsh, + RegSub, + RegXor, ) diff --git a/pytket/tests/mapping_test.py b/pytket/tests/mapping_test.py index 0093e46733..d8419cf660 100644 --- a/pytket/tests/mapping_test.py +++ b/pytket/tests/mapping_test.py @@ -11,31 +11,32 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from types import MappingProxyType +from typing import Dict, Tuple + +import numpy as np + +from pytket import Circuit, OpType +from pytket.architecture import Architecture +from pytket.circuit import CircBox, Node, PhasePolyBox, Qubit, UnitID from pytket.circuit.named_types import UnitIdMap from pytket.mapping import ( - MappingManager, - RoutingMethodCircuit, - LexiRouteRoutingMethod, + AASLabellingMethod, AASRouteRoutingMethod, + BoxDecompositionRoutingMethod, LexiLabellingMethod, - AASLabellingMethod, + LexiRouteRoutingMethod, + MappingManager, MultiGateReorderRoutingMethod, - BoxDecompositionRoutingMethod, + RoutingMethodCircuit, ) -from pytket.architecture import Architecture -from pytket import Circuit, OpType -from pytket.circuit import UnitID, Node, PhasePolyBox, Qubit, CircBox from pytket.placement import Placement -from typing import Tuple, Dict -import numpy as np # simple deterministic heuristic used for testing purposes def route_subcircuit_func( circuit: Circuit, architecture: Architecture -) -> Tuple[bool, Circuit, UnitIdMap, UnitIdMap]: +) -> tuple[bool, Circuit, UnitIdMap, UnitIdMap]: # make a replacement circuit with identical unitds replacement_circuit = Circuit() for qb in circuit.qubits: @@ -112,7 +113,7 @@ def route_subcircuit_func( def route_subcircuit_func_false( circuit: Circuit, architecture: Architecture -) -> Tuple[bool, Circuit, Dict[UnitID, UnitID], Dict[UnitID, UnitID]]: +) -> tuple[bool, Circuit, dict[UnitID, UnitID], dict[UnitID, UnitID]]: return (False, Circuit(), {}, {}) diff --git a/pytket/tests/measurement_setup_test.py b/pytket/tests/measurement_setup_test.py index 0beb01c7b7..fa25c1f480 100644 --- a/pytket/tests/measurement_setup_test.py +++ b/pytket/tests/measurement_setup_test.py @@ -12,19 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Any + from pytket import logging from pytket.circuit import Circuit, Qubit -from pytket.pauli import Pauli, QubitPauliString from pytket.partition import ( - PauliPartitionStrat, MeasurementBitMap, MeasurementSetup, + PauliPartitionStrat, measurement_reduction, ) - -import pytest -import platform -from typing import Any +from pytket.pauli import Pauli, QubitPauliString def test_empty_setup() -> None: diff --git a/pytket/tests/mitigation_test.py b/pytket/tests/mitigation_test.py index d6599a4335..aa339e0b7f 100644 --- a/pytket/tests/mitigation_test.py +++ b/pytket/tests/mitigation_test.py @@ -12,26 +12,27 @@ # See the License for the specific language governing permissions and # limitations under the License. -import tempfile import json +import tempfile +from collections import Counter +from math import ceil +from typing import Dict, List, Tuple, cast + +import pytest -from pytket.utils.spam import SpamCorrecter, compress_counts -from pytket.circuit import Node, Circuit, Qubit -from pytket.mapping import MappingManager, LexiLabellingMethod, LexiRouteRoutingMethod from pytket.architecture import Architecture -from pytket.placement import place_with_map +from pytket.backends.backendresult import BackendResult +from pytket.circuit import Circuit, Node, Qubit +from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager from pytket.passes import DelayMeasures -from typing import List, Dict, Counter, Tuple, cast +from pytket.placement import place_with_map from pytket.utils.outcomearray import OutcomeArray -from math import ceil -from pytket.backends.backendresult import BackendResult -import pytest +from pytket.utils.spam import SpamCorrecter, compress_counts def test_spam_integration() -> None: - SEED = 120 # test data, to avoid using backend - calib_results: List[Dict[Tuple[int, ...], int]] = [ + calib_results: list[dict[tuple[int, ...], int]] = [ { (0, 0, 0): 742, (0, 0, 1): 84, @@ -73,7 +74,7 @@ def test_spam_integration() -> None: (1, 1, 1): 424, }, ] - bellres_counts: Dict[Tuple[int, ...], int] = { + bellres_counts: dict[tuple[int, ...], int] = { (0, 0, 0): 406, (0, 0, 1): 111, (0, 1, 0): 38, @@ -116,7 +117,7 @@ def test_spam_integration() -> None: mm.route_circuit(rbell, [LexiLabellingMethod(), LexiRouteRoutingMethod()]) def check_correction( - counts0: Dict[Tuple[int, ...], int], counts1: Dict[Tuple[int, ...], int] + counts0: dict[tuple[int, ...], int], counts1: dict[tuple[int, ...], int] ) -> bool: if ( counts0[(0, 0, 0)] > counts1[(0, 0, 0)] @@ -233,7 +234,7 @@ def test_spam_routing() -> None: (1, 1, 1, 1): 167, } - calib_results: List[Dict[Tuple[int, ...], int]] = [ + calib_results: list[dict[tuple[int, ...], int]] = [ { (0, 0, 0, 0): 659, (0, 0, 0, 1): 65, diff --git a/pytket/tests/passes_script_test.py b/pytket/tests/passes_script_test.py index 5deda534e2..14c803e305 100644 --- a/pytket/tests/passes_script_test.py +++ b/pytket/tests/passes_script_test.py @@ -12,11 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from lark import Lark import pytest +from lark import Lark + from pytket.circuit import OpType -from pytket.passes import compilation_pass_from_script, compilation_pass_grammar from pytket.passes import ( + BasePass, CliffordSimp, ContextSimp, DecomposeBoxes, @@ -26,9 +27,12 @@ OptimisePhaseGadgets, PauliSimp, PauliSquash, + RepeatPass, + SequencePass, SimplifyInitial, + compilation_pass_from_script, + compilation_pass_grammar, ) -from pytket.passes import BasePass, RepeatPass, SequencePass from pytket.transform import CXConfigType, PauliSynthStrat diff --git a/pytket/tests/passes_serialisation_test.py b/pytket/tests/passes_serialisation_test.py index 193ece5786..59db4ade06 100644 --- a/pytket/tests/passes_serialisation_test.py +++ b/pytket/tests/passes_serialisation_test.py @@ -13,42 +13,40 @@ # limitations under the License. import json -import pytest -from referencing import Registry -from referencing.jsonschema import DRAFT7 -from jsonschema import Draft7Validator, ValidationError # type: ignore from pathlib import Path from typing import Any, Dict, List -from sympy import Expr +import pytest +from jsonschema import Draft7Validator, ValidationError # type: ignore +from referencing import Registry +from referencing.jsonschema import DRAFT7 -from pytket.circuit import Node, Circuit, Qubit, OpType -from pytket.predicates import Predicate -from pytket.architecture import Architecture -from pytket.placement import Placement, GraphPlacement import pytket.circuit_library as _library - +from pytket.architecture import Architecture +from pytket.circuit import Circuit, Node, OpType, Qubit +from pytket.circuit.named_types import ParamType +from pytket.mapping import ( + BoxDecompositionRoutingMethod, + LexiLabellingMethod, + LexiRouteRoutingMethod, + MultiGateReorderRoutingMethod, +) from pytket.passes import ( + AASRouting, BasePass, - SequencePass, + CommuteThroughMultis, + CustomPass, + DefaultMappingPass, + FullMappingPass, + RebaseCustom, RemoveRedundancies, RepeatUntilSatisfiedPass, - CommuteThroughMultis, RepeatWithMetricPass, - RebaseCustom, - CXMappingPass, - FullMappingPass, - DefaultMappingPass, - AASRouting, + SequencePass, SquashCustom, ) -from pytket.mapping import ( - LexiLabellingMethod, - LexiRouteRoutingMethod, - MultiGateReorderRoutingMethod, - BoxDecompositionRoutingMethod, -) -from pytket.circuit.named_types import ParamType +from pytket.placement import GraphPlacement, Placement +from pytket.predicates import Predicate def standard_pass_dict(content: Dict[str, Any]) -> Dict[str, Any]: @@ -298,6 +296,9 @@ def nonparam_predicate_dict(name: str) -> Dict[str, Any]: "max_tqe_candidates": 100, "seed": 2, "allow_zzphase": True, + "thread_timeout": 5000, + "only_reduce": False, + "trials": 1, } ), # lists must be sorted by OpType value @@ -589,9 +590,8 @@ def check_arc_dict(arc: Architecture, d: dict) -> bool: if d["links"] != links: return False - else: - nodes = [Node(n[0], n[1]) for n in d["nodes"]] - return set(nodes) == set(arc.nodes) + nodes = [Node(n[0], n[1]) for n in d["nodes"]] + return set(nodes) == set(arc.nodes) def test_pass_deserialisation_only() -> None: @@ -698,7 +698,7 @@ def tk1_rep(a: ParamType, b: ParamType, c: ParamType) -> Circuit: np_pass = dm_pass_0.get_sequence()[2] d_pass = dm_pass.get_sequence()[1] assert d_pass.to_dict()["StandardPass"]["name"] == "DelayMeasures" - assert d_pass.to_dict()["StandardPass"]["allow_partial"] == False + assert d_pass.to_dict()["StandardPass"]["allow_partial"] == False # noqa: E712 assert p_pass.to_dict()["StandardPass"]["name"] == "PlacementPass" assert np_pass.to_dict()["StandardPass"]["name"] == "NaivePlacementPass" assert r_pass.to_dict()["StandardPass"]["name"] == "RoutingPass" @@ -771,3 +771,15 @@ def no_CX(circ: Circuit) -> bool: rps.to_dict()["RepeatUntilSatisfiedPass"]["predicate"]["type"] == "UserDefinedPredicate" ) + + +def test_custom_deserialisation() -> None: + def t(c: Circuit) -> Circuit: + return Circuit(2).CX(0, 1) + + custom_pass_post = BasePass.from_dict( + CustomPass(t, label="test").to_dict(), {"test": t} + ) + c: Circuit = Circuit(3).H(0).H(1).H(2) + custom_pass_post.apply(c) + assert c == Circuit(2).CX(0, 1) diff --git a/pytket/tests/placement_test.py b/pytket/tests/placement_test.py index 346d1861aa..8ce27d75fd 100644 --- a/pytket/tests/placement_test.py +++ b/pytket/tests/placement_test.py @@ -12,25 +12,26 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json from pathlib import Path + +import pytest + from pytket import Circuit -from pytket.circuit import Node, Qubit from pytket.architecture import Architecture, FullyConnected +from pytket.circuit import Node, Qubit +from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager +from pytket.passes import DefaultMappingPass, PauliSimp from pytket.placement import ( - Placement, - LinePlacement, GraphPlacement, + LinePlacement, NoiseAwarePlacement, - place_with_map, + Placement, place_fully_connected, + place_with_map, ) -from pytket.passes import PauliSimp, DefaultMappingPass -from pytket.mapping import MappingManager, LexiRouteRoutingMethod, LexiLabellingMethod from pytket.qasm import circuit_from_qasm -import json -import pytest - def test_placements() -> None: test_coupling = [(0, 1), (1, 2), (1, 3), (4, 1), (4, 5)] @@ -92,13 +93,13 @@ def test_placements() -> None: def test_placements_serialization() -> None: with open( - Path(__file__).resolve().parent / "json_test_files" / "placements.json", "r" + Path(__file__).resolve().parent / "json_test_files" / "placements.json" ) as f: - dict = json.load(f) - base_pl_serial = dict["base_placement"] - line_pl_serial = dict["line_placement"] - graph_pl_serial = dict["graph_placement"] - noise_pl_serial = dict["noise_placement"] + d = json.load(f) + base_pl_serial = d["base_placement"] + line_pl_serial = d["line_placement"] + graph_pl_serial = d["graph_placement"] + noise_pl_serial = d["noise_placement"] assert Placement.from_dict(base_pl_serial).to_dict() == base_pl_serial assert LinePlacement.from_dict(line_pl_serial).to_dict() == line_pl_serial diff --git a/pytket/tests/predicates_test.py b/pytket/tests/predicates_test.py index ea805321a4..1bda5404cc 100644 --- a/pytket/tests/predicates_test.py +++ b/pytket/tests/predicates_test.py @@ -11,99 +11,95 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import sympy +from typing import Any, Dict, List + +import numpy as np import pytest -from pytket import logging +from sympy import Symbol + +from pytket.architecture import Architecture from pytket.circuit import ( + Bit, + CircBox, Circuit, - OpType, + Conditional, + MultiBitOp, + Node, Op, - CircBox, + OpType, PauliExpBox, - Unitary1qBox, - Unitary2qBox, - Node, Qubit, - UnitID, - Conditional, - Bit, RangePredicateOp, SetBitsOp, - MultiBitOp, + Unitary1qBox, + Unitary2qBox, ) from pytket.circuit.named_types import ParamType, RenameUnitsMap -from pytket.pauli import Pauli +from pytket.circuit.named_types import ParamType as Param +from pytket.mapping import ( + LexiLabellingMethod, + LexiRouteRoutingMethod, +) from pytket.passes import ( - SequencePass, - RemoveRedundancies, - SynthesiseTket, - SynthesiseTK, - SynthesiseUMD, - RepeatUntilSatisfiedPass, + AASRouting, + AutoRebase, + CliffordPushThroughMeasures, + CliffordResynthesis, + CliffordSimp, + CnXPairwiseDecomposition, CommuteThroughMultis, - RepeatPass, - DecomposeMultiQubitsCX, + CXMappingPass, DecomposeBoxes, - SquashTK1, - SquashRzPhasedX, - RepeatWithMetricPass, - RebaseCustom, + DecomposeMultiQubitsCX, + DecomposeSwapsToCircuit, + DecomposeSwapsToCXs, + DefaultMappingPass, EulerAngleReduction, - RoutingPass, - CXMappingPass, - PlacementPass, - NaivePlacementPass, - RenameQubitsPass, + FlattenRelabelRegistersPass, FullMappingPass, - DefaultMappingPass, - AASRouting, - DecomposeSwapsToCXs, - DecomposeSwapsToCircuit, + GreedyPauliSimp, + NaivePlacementPass, PauliSimp, - ThreeQubitSquash, + PauliSquash, + PeepholeOptimise2Q, + PlacementPass, + RebaseCustom, RebaseTket, - RemoveDiscarded, - SimplifyMeasured, - SimplifyInitial, RemoveBarriers, - PauliSquash, - AutoRebase, - AutoSquash, - auto_rebase_pass, - auto_squash_pass, - ZZPhaseToRz, - CnXPairwiseDecomposition, + RemoveDiscarded, RemoveImplicitQubitPermutation, - FlattenRelabelRegistersPass, + RemoveRedundancies, + RenameQubitsPass, + RepeatPass, + RepeatUntilSatisfiedPass, + RepeatWithMetricPass, RoundAngles, - PeepholeOptimise2Q, - CliffordResynthesis, - CliffordPushThroughMeasures, - CliffordSimp, + RoutingPass, + SequencePass, + SimplifyInitial, + SimplifyMeasured, + SquashRzPhasedX, + SquashTK1, + SynthesiseTK, + SynthesiseTket, + SynthesiseUMD, + ThreeQubitSquash, ZXGraphlikeOptimisation, - GreedyPauliSimp, + ZZPhaseToRz, + auto_rebase_pass, + auto_squash_pass, ) +from pytket.pauli import Pauli +from pytket.placement import GraphPlacement, Placement from pytket.predicates import ( - GateSetPredicate, - NoClassicalControlPredicate, - DirectednessPredicate, - NoBarriersPredicate, CompilationUnit, + DirectednessPredicate, + GateSetPredicate, MaxNClRegPredicate, + NoBarriersPredicate, + NoClassicalControlPredicate, ) -from pytket.mapping import ( - LexiLabellingMethod, - LexiRouteRoutingMethod, -) -from pytket.architecture import Architecture -from pytket.placement import Placement, GraphPlacement -from pytket.transform import Transform, PauliSynthStrat, CXConfigType -import numpy as np -from sympy import Symbol -from typing import Dict, Any, List - -from pytket.circuit.named_types import ParamType as Param - +from pytket.transform import CXConfigType, PauliSynthStrat, Transform circ2 = Circuit(1) circ2.Rx(0.25, 0) @@ -307,7 +303,7 @@ def test_routing_and_placement_pass() -> None: def test_default_mapping_pass() -> None: circ = Circuit() - q = circ.add_q_register("q", 6) + circ.add_q_register("q", 6) circ.CX(0, 1) circ.H(0) circ.Z(1) @@ -343,7 +339,7 @@ def test_default_mapping_pass() -> None: def test_default_mapping_pass_phase_poly_aas() -> None: circ = Circuit() - q = circ.add_q_register("q", 5) + circ.add_q_register("q", 5) circ.CX(0, 1) circ.H(0) circ.Z(1) @@ -365,7 +361,7 @@ def test_default_mapping_pass_phase_poly_aas() -> None: def test_rename_qubits_pass() -> None: circ = Circuit() - qbs = circ.add_q_register("a", 2) + circ.add_q_register("a", 2) circ.CX(Qubit("a", 0), Qubit("a", 1)) qm = {Qubit("a", 0): Qubit("b", 1), Qubit("a", 1): Qubit("b", 0)} p = RenameQubitsPass(qm) @@ -612,18 +608,18 @@ def test_squash_chains() -> None: def test_apply_pass_with_callbacks() -> None: class CallbackHandler: def __init__(self) -> None: - self.pass_names: List[str] = [] + self.pass_names: list[str] = [] - def before_apply(self, cu: CompilationUnit, config: Dict[str, Any]) -> None: + def before_apply(self, cu: CompilationUnit, config: dict[str, Any]) -> None: if "StandardPass" in config: self.pass_names.append(config["StandardPass"]["name"]) else: self.pass_names.append(config["pass_class"]) - def after_apply(self, cu: CompilationUnit, config: Dict[str, Any]) -> None: + def after_apply(self, cu: CompilationUnit, config: dict[str, Any]) -> None: return - def compile(circ: Circuit, handler: CallbackHandler) -> bool: + def apply_pass(circ: Circuit, handler: CallbackHandler) -> bool: p = SequencePass([CommuteThroughMultis(), RemoveRedundancies()]) return p.apply(circ, handler.before_apply, handler.after_apply) @@ -633,7 +629,7 @@ def compile(circ: Circuit, handler: CallbackHandler) -> bool: circ.CX(0, 1) handler = CallbackHandler() - compile(circ, handler) + apply_pass(circ, handler) assert circ.n_gates_of_type(OpType.CX) == 1 assert len(handler.pass_names) == 3 @@ -846,6 +842,14 @@ def test_conditional_phase() -> None: assert any(any_check_list) +def test_rz_sx_decomp() -> None: + c = Circuit(1).TK1(0, 1.5, 0, 0) + AutoRebase({OpType.CX, OpType.SX, OpType.Rz}).apply(c) + comp = Circuit(1).Rz(1, 0).SX(0).Rz(1, 0) + comp.add_phase(1.75) + assert c == comp + + def test_flatten_relabel_pass() -> None: c = Circuit(3) c.H(1).H(2) @@ -894,7 +898,7 @@ def test_PeepholeOptimise2Q() -> None: perm = c.implicit_qubit_permutation() assert any(k != v for k, v in perm.items()) c = Circuit(2).CX(0, 1).CX(1, 0) - assert PeepholeOptimise2Q(allow_swaps=False).apply(c) == False + assert not PeepholeOptimise2Q(allow_swaps=False).apply(c) perm = c.implicit_qubit_permutation() assert all(k == v for k, v in perm.items()) @@ -1031,8 +1035,8 @@ def test_greedy_pauli_synth() -> None: rega[0], regb[0] ).SWAP(regb[1], rega[0]) d = circ.copy() - pss = GreedyPauliSimp(0.5, 0.5) - assert pss.apply(d) + assert GreedyPauliSimp(0.5, 0.5, thread_timeout=10, trials=5).apply(d) + assert np.allclose(circ.get_unitary(), d.get_unitary()) assert d.name == "test" # test gateset @@ -1054,7 +1058,7 @@ def test_greedy_pauli_synth() -> None: circ.measure_all() circ.Reset(0) circ.add_pauliexpbox(pg1, [2, 3]) - assert GreedyPauliSimp(0.5, 0.5, 100, 100, 0, True).apply(circ) + assert GreedyPauliSimp(0.5, 0.5, 100, 100, 0, True, 100).apply(circ) # PauliExpBoxes implemented using ZZPhase d = Circuit(4, 4, name="test") d.H(0) @@ -1078,15 +1082,25 @@ def test_greedy_pauli_synth() -> None: GreedyPauliSimp().apply(circ) err_msg = "Predicate requirements are not satisfied" assert err_msg in str(e.value) + # large circuit that doesn't complete within thread_timeout argument + c = Circuit(13) + for _ in range(20): + for i in range(13): + for j in range(i + 1, 13): + c.CX(i, j) + c.Rz(0.23, j) + c.H(i) + assert not GreedyPauliSimp(thread_timeout=1).apply(c) + assert GreedyPauliSimp().apply(c) def test_auto_rebase_deprecation(recwarn: Any) -> None: - p = auto_rebase_pass({OpType.TK1, OpType.CX}) + _ = auto_rebase_pass({OpType.TK1, OpType.CX}) assert len(recwarn) == 1 w = recwarn.pop(DeprecationWarning) assert issubclass(w.category, DeprecationWarning) assert "deprecated" in str(w.message) - p = auto_squash_pass({OpType.TK1}) + _ = auto_squash_pass({OpType.TK1}) assert len(recwarn) == 1 w = recwarn.pop(DeprecationWarning) assert issubclass(w.category, DeprecationWarning) @@ -1112,3 +1126,4 @@ def test_auto_rebase_deprecation(recwarn: Any) -> None: test_rebase_custom_tk2() test_selectively_decompose_boxes() test_clifford_push_through_measures() + test_rz_sx_decomp() diff --git a/pytket/tests/pytket_config_test.py b/pytket/tests/pytket_config_test.py index fa307d23b0..3619c39d4e 100644 --- a/pytket/tests/pytket_config_test.py +++ b/pytket/tests/pytket_config_test.py @@ -12,18 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from dataclasses import dataclass -from typing import Any, ClassVar, Dict, Optional, Type, Iterator import json +from collections.abc import Iterator +from dataclasses import dataclass from pathlib import Path +from typing import Any, ClassVar, Dict, Optional, Type -from jsonschema import validate # type: ignore import pytest +from jsonschema import validate # type: ignore from pytket.config import ( - get_config_file_path, PytketConfig, PytketExtConfig, + get_config_file_path, load_config_file, write_config_file, ) @@ -33,25 +34,25 @@ class SampleExtConfig(PytketExtConfig): ext_dict_key: ClassVar[str] = "tests_sample" - field1: Optional[str] - field2: Optional[int] + field1: str | None + field2: int | None @classmethod def from_extension_dict( - cls: Type["SampleExtConfig"], ext_dict: Dict[str, Any] + cls: type["SampleExtConfig"], ext_dict: dict[str, Any] ) -> "SampleExtConfig": - return cls(ext_dict.get("field1", None), ext_dict.get("field2", None)) + return cls(ext_dict.get("field1"), ext_dict.get("field2")) def test_pytket_config() -> None: config_file = get_config_file_path() assert config_file.exists() - with open(config_file, "r") as f: + with open(config_file) as f: config_dict = json.load(f) curr_file_path = Path(__file__).resolve().parent - with open(curr_file_path.parent.parent / "schemas/pytket_config_v1.json", "r") as f: + with open(curr_file_path.parent.parent / "schemas/pytket_config_v1.json") as f: schema = json.load(f) validate(instance=config_dict, schema=schema) diff --git a/pytket/tests/qasm_test.py b/pytket/tests/qasm_test.py index 8ed36b7fb4..a3824c03fe 100644 --- a/pytket/tests/qasm_test.py +++ b/pytket/tests/qasm_test.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from io import StringIO import re +from io import StringIO from pathlib import Path from typing import List @@ -21,42 +21,42 @@ from pytket._tket.unit_id import _TEMP_BIT_NAME, _TEMP_BIT_REG_BASE from pytket.circuit import ( + Bit, + BitRegister, Circuit, + CustomGate, OpType, - fresh_symbol, Qubit, - Bit, + RangePredicateOp, + fresh_symbol, + if_not_bit, reg_eq, - reg_neq, - reg_lt, + reg_geq, reg_gt, reg_leq, - reg_geq, - if_not_bit, - BitRegister, - CustomGate, - RangePredicateOp, + reg_lt, + reg_neq, ) from pytket.circuit.decompose_classical import DecomposeClassicalError from pytket.circuit.logic_exp import BitWiseOp, create_bit_logic_exp +from pytket.passes import DecomposeBoxes, DecomposeClassicalExp from pytket.qasm import ( circuit_from_qasm, - circuit_to_qasm, circuit_from_qasm_str, - circuit_to_qasm_str, circuit_from_qasm_wasm, + circuit_to_qasm, + circuit_to_qasm_str, ) -from pytket.qasm.qasm import QASMParseError, QASMUnsupportedError from pytket.qasm.includes.load_includes import ( _get_declpath, - _get_files, _get_defpath, - _write_defs, - _write_decls, + _get_files, _load_gdict, + _write_decls, + _write_defs, ) +from pytket.qasm.qasm import QASMParseError, QASMUnsupportedError from pytket.transform import Transform -from pytket.passes import DecomposeClassicalExp, DecomposeBoxes curr_file_path = Path(__file__).resolve().parent @@ -94,6 +94,36 @@ def test_qasm_correct() -> None: assert str(coms3) == correct_str3 +def test_long_registers() -> None: + fname = str(curr_file_path / "qasm_test_files/longreg.qasm") + c = circuit_from_qasm(fname, maxwidth=64) + assert c.n_qubits == 0 + assert c.n_bits == 79 + assert len(c.c_registers) == 5 + for creg in c.c_registers: + assert creg.size <= 64 + + +def test_long_registers_2() -> None: + fname = str(curr_file_path / "qasm_test_files/longreg.qasm") + c = circuit_from_qasm(fname, maxwidth=100) + assert c.n_qubits == 0 + assert c.n_bits == 79 + assert len(c.c_registers) == 4 + for creg in c.c_registers: + assert creg.size <= 100 + + +def test_incompete_registers() -> None: + c = Circuit(1) + c.add_bit(Bit("d", 1)) + c.add_bit(Bit("d", 2)) + c.Measure(Qubit(0), Bit("d", 1)) + with pytest.raises(QASMUnsupportedError) as errorinfo: + circuit_to_qasm_str(c, header="hqslib1") + assert "Circuit contains an invalid classical register d." in str(errorinfo.value) + + def test_qasm_qubit() -> None: with pytest.raises(RuntimeError) as errorinfo: fname = str(curr_file_path / "qasm_test_files/test2.qasm") @@ -147,7 +177,7 @@ def test_qasm_roundtrip() -> None: def test_qasm_str_roundtrip() -> None: - with open(curr_file_path / "qasm_test_files/test1.qasm", "r") as f: + with open(curr_file_path / "qasm_test_files/test1.qasm") as f: c = circuit_from_qasm_str(f.read()) qasm_str = circuit_to_qasm_str(c) c2 = circuit_from_qasm_str(qasm_str) @@ -220,6 +250,9 @@ def test_conditional_gates() -> None: def test_named_conditional_barrier() -> None: circ = Circuit(2, 2) + circ.add_bit(Bit("test", 0)) + circ.add_bit(Bit("test", 1)) + circ.add_bit(Bit("test", 2)) circ.add_bit(Bit("test", 3)) circ.Z(0, condition_bits=[0, 1], condition_value=2) circ.add_conditional_barrier( @@ -414,7 +447,7 @@ def test_h1_rzz() -> None: hqs_qasm_str = circuit_to_qasm_str(c, header="hqslib1") assert "RZZ" in hqs_qasm_str - with open(str(curr_file_path / "qasm_test_files/zzphase.qasm"), "r") as f: + with open(str(curr_file_path / "qasm_test_files/zzphase.qasm")) as f: fread_str = str(f.read()) assert str(hqs_qasm_str) == fread_str @@ -432,14 +465,18 @@ def test_extended_qasm() -> None: assert circuit_to_qasm_str(c2, "hqslib1") - assert not DecomposeClassicalExp().apply(c) + with pytest.raises(DecomposeClassicalError): + DecomposeClassicalExp().apply(c) -def test_decomposable_extended() -> None: +@pytest.mark.parametrize("use_clexpr", [True, False]) +def test_decomposable_extended(use_clexpr: bool) -> None: fname = str(curr_file_path / "qasm_test_files/test18.qasm") out_fname = str(curr_file_path / "qasm_test_files/test18_output.qasm") - c = circuit_from_qasm_wasm(fname, "testfile.wasm", maxwidth=64, use_clexpr=True) + c = circuit_from_qasm_wasm( + fname, "testfile.wasm", maxwidth=64, use_clexpr=use_clexpr + ) DecomposeClassicalExp().apply(c) out_qasm = circuit_to_qasm_str(c, "hqslib1", maxwidth=64) @@ -451,9 +488,9 @@ def test_opaque() -> None: c = circuit_from_qasm_str( 'OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[4];\nopaque myopaq() q1, q2;\n myopaq() q[0], q[1];' ) - with pytest.raises(QASMUnsupportedError) as e: - circuit_to_qasm_str(c) - assert "Empty CustomGates and opaque gates are not supported" in str(e.value) + assert ( + circuit_to_qasm_str(c) == 'OPENQASM 2.0;\ninclude "qelib1.inc";\n\nqreg q[4];\n' + ) def test_alternate_encoding() -> None: @@ -462,7 +499,7 @@ def test_alternate_encoding() -> None: "utf-32": str(curr_file_path / "qasm_test_files/utf32.qasm"), } for enc, fil in encoded_files.items(): - with pytest.raises(Exception) as e: + with pytest.raises(Exception): circuit_from_qasm(fil) c = circuit_from_qasm(fil, encoding=enc) assert c.n_gates == 6 @@ -574,7 +611,7 @@ def test_scratch_bits_filtering() -> None: assert _TEMP_BIT_NAME not in qstr qasm_out = str(curr_file_path / "qasm_test_files/testout6.qasm") circuit_to_qasm(c, qasm_out, "hqslib1") - with open(qasm_out, "r") as f: + with open(qasm_out) as f: assert _TEMP_BIT_NAME not in f.read() # test keeping used @@ -586,7 +623,7 @@ def test_scratch_bits_filtering() -> None: qstr = circuit_to_qasm_str(c, "hqslib1") assert _TEMP_BIT_NAME in qstr circuit_to_qasm(c, qasm_out, "hqslib1") - with open(qasm_out, "r") as f: + with open(qasm_out) as f: assert _TEMP_BIT_NAME in f.read() # test multiple scratch registers @@ -602,7 +639,8 @@ def test_scratch_bits_filtering() -> None: creg {_TEMP_BIT_NAME}_1[32]; {_TEMP_BIT_NAME}[0] = (a[0] ^ b[0]); if({_TEMP_BIT_NAME}[0]==1) x q[0]; - """ + """, + maxwidth=64, ) assert c.get_c_register(_TEMP_BIT_NAME) assert c.get_c_register(f"{_TEMP_BIT_NAME}_1") @@ -610,7 +648,7 @@ def test_scratch_bits_filtering() -> None: assert _TEMP_BIT_NAME in qstr assert f"{_TEMP_BIT_NAME}_1" not in qstr circuit_to_qasm(c, qasm_out, "hqslib1") - with open(qasm_out, "r") as f: + with open(qasm_out) as f: fstr = f.read() assert _TEMP_BIT_NAME in fstr assert f"{_TEMP_BIT_NAME}_1" not in fstr @@ -630,7 +668,7 @@ def test_scratch_bits_filtering() -> None: qstr = circuit_to_qasm_str(c, "hqslib1") assert f"creg {_TEMP_BIT_REG_BASE}_0[32]" in qstr circuit_to_qasm(c, qasm_out, "hqslib1") - with open(qasm_out, "r") as f: + with open(qasm_out) as f: fstr = f.read() assert f"creg {_TEMP_BIT_REG_BASE}_0[32]" in fstr @@ -694,7 +732,7 @@ def test_RZZ_read_from() -> None: def test_conditional_expressions() -> None: - def cond_circ(bits: List[int]) -> Circuit: + def cond_circ(bits: list[int]) -> Circuit: c = Circuit(4, 4) c.X(0) c.X(1) @@ -836,14 +874,14 @@ def test_classical_expbox_arg_order(use_clexpr: bool) -> None: qasm = """ OPENQASM 2.0; include "hqslib1.inc"; - + qreg q[1]; - + creg a[4]; creg b[4]; creg c[4]; creg d[4]; - + c = a ^ b | d; """ @@ -865,11 +903,11 @@ def test_register_name_check() -> None: qasm = """ OPENQASM 2.0; include "hqslib1.inc"; - + qreg Q[1]; """ with pytest.raises(QASMParseError) as e: - circ = circuit_from_qasm_str(qasm) + _ = circuit_from_qasm_str(qasm) err_msg = "Invalid register definition 'Q[1]'" assert err_msg in str(e.value) @@ -1026,16 +1064,62 @@ def test_conditional_multi_line_ops() -> None: def test_conditional_range_predicate() -> None: - range_predicate = RangePredicateOp(6, 0, 27) - c = Circuit(0, 8) - c.add_gate(range_predicate, [0, 1, 2, 3, 4, 5, 6], condition=Bit(7)) - # remove once https://github.com/CQCL/tket/issues/1508 - # is resolved + range_predicate = RangePredicateOp(2, 0, 2) + c = Circuit(0, 5) + c.add_gate(range_predicate, [1, 2, 4]) + # https://github.com/CQCL/tket/issues/1642 with pytest.raises(Exception) as errorinfo: - circuit_to_qasm_str(c, header="hqslib1") - assert "Conditional RangePredicate is currently unsupported." in str( + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert "RangePredicate conditions must be an entire classical register" in str( errorinfo.value ) + # https://github.com/CQCL/tket/issues/1508 + range_predicate = RangePredicateOp(6, 0, 27) + c = Circuit(0, 6) + c.add_gate(range_predicate, [0, 1, 2, 3, 4, 5, 5], condition=Bit(5)) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +creg c[6]; +creg tk_SCRATCH_BITREG_0[4]; +if(c[5]==1) tk_SCRATCH_BITREG_0[0] = 1; +if(c<=27) tk_SCRATCH_BITREG_0[1] = 1; +tk_SCRATCH_BITREG_0[2] = tk_SCRATCH_BITREG_0[0] & tk_SCRATCH_BITREG_0[1]; +tk_SCRATCH_BITREG_0[3] = tk_SCRATCH_BITREG_0[0] & (~ tk_SCRATCH_BITREG_0[1]); +if(tk_SCRATCH_BITREG_0[2]==1) c[5] = 1; +if(tk_SCRATCH_BITREG_0[3]==1) c[5] = 0; +""" + ) + # more test + range_predicate = RangePredicateOp(2, 0, 2) + c = Circuit() + reg_a = c.add_c_register("a", 2) + reg_b = c.add_c_register("b", 2) + reg_d = c.add_c_register("d", 1) + c.add_gate( + range_predicate, reg_a.to_list() + reg_d.to_list(), condition=reg_gt(reg_b, 1) + ) + qasm = circuit_to_qasm_str(c, header="hqslib1") + assert ( + qasm + == """OPENQASM 2.0; +include "hqslib1.inc"; + +creg a[2]; +creg b[2]; +creg d[1]; +creg tk_SCRATCH_BITREG_0[4]; +if(b>=2) tk_SCRATCH_BITREG_0[0] = 1; +if(a<=2) tk_SCRATCH_BITREG_0[1] = 1; +tk_SCRATCH_BITREG_0[2] = tk_SCRATCH_BITREG_0[0] & tk_SCRATCH_BITREG_0[1]; +tk_SCRATCH_BITREG_0[3] = tk_SCRATCH_BITREG_0[0] & (~ tk_SCRATCH_BITREG_0[1]); +if(tk_SCRATCH_BITREG_0[2]==1) d[0] = 1; +if(tk_SCRATCH_BITREG_0[3]==1) d[0] = 0; +""" + ) def test_range_with_maxwidth() -> None: @@ -1154,7 +1238,8 @@ def test_multibitop() -> None: test_hqs_conditional_params() test_barrier() test_barrier_2() - test_decomposable_extended() + test_decomposable_extended(True) + test_decomposable_extended(False) test_alternate_encoding() test_header_stops_gate_definition() test_tk2_definition() diff --git a/pytket/tests/qasm_test_files/longreg.qasm b/pytket/tests/qasm_test_files/longreg.qasm new file mode 100644 index 0000000000..7fbe3d6dcd --- /dev/null +++ b/pytket/tests/qasm_test_files/longreg.qasm @@ -0,0 +1,83 @@ +OPENQASM 2.0; +include "hqslib1.inc"; + +creg a[1]; +creg b[1]; +creg c[1]; + +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; +if(c!=0) a = b; diff --git a/pytket/tests/qasm_test_files/test18_output.qasm b/pytket/tests/qasm_test_files/test18_output.qasm index c635f280ab..f2ab0b7d65 100644 --- a/pytket/tests/qasm_test_files/test18_output.qasm +++ b/pytket/tests/qasm_test_files/test18_output.qasm @@ -6,16 +6,22 @@ creg a[2]; creg b[3]; creg c[4]; creg d[1]; +creg tk_SCRATCH_BIT[7]; +creg tk_SCRATCH_BITREG_0[64]; c = 2; +tk_SCRATCH_BITREG_0[0] = b[0] & a[0]; +tk_SCRATCH_BITREG_0[1] = b[1] & a[1]; c[0] = a[0]; c[1] = a[1]; -if(b!=2) c[1] = ((b[1] & a[1]) | a[0]); -c = ((b & a) | d); -d[0] = (a[0] ^ 1); -a = CCE(a, b); +if(b!=2) tk_SCRATCH_BIT[6] = b[1] & a[1]; +c[0] = tk_SCRATCH_BITREG_0[0] | d[0]; +if(b!=2) c[1] = tk_SCRATCH_BIT[6] | a[0]; +tk_SCRATCH_BIT[6] = 1; +d[0] = a[0] ^ tk_SCRATCH_BIT[6]; if(c>=2) h q[0]; -CCE(c); +a = CCE(a, b); if(c<=2) h q[0]; +CCE(c); if(c<=1) h q[0]; if(c>=3) h q[0]; if(c!=2) h q[0]; diff --git a/pytket/tests/qubitpaulioperator_test.py b/pytket/tests/qubitpaulioperator_test.py index b27a5cfb87..34a3637404 100644 --- a/pytket/tests/qubitpaulioperator_test.py +++ b/pytket/tests/qubitpaulioperator_test.py @@ -12,20 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -import copy -from hypothesis import given, settings import json import pickle -import pytest import numpy as np -from sympy import Symbol, re, im +import pytest +import strategies as st # type: ignore +from hypothesis import given, settings +from sympy import Symbol, im, re -from pytket.utils import QubitPauliOperator -from pytket.pauli import Pauli, QubitPauliString, pauli_string_mult from pytket.circuit import Qubit - -import strategies as st # type: ignore +from pytket.pauli import Pauli, QubitPauliString +from pytket.utils import QubitPauliOperator def test_QubitPauliOperator_addition() -> None: @@ -150,7 +148,7 @@ def test_QubitPauliOperator_compression() -> None: op = QubitPauliOperator({qpsXY: 2, qpsZI: 1e-11j * x, qpsYY: 1e-11 * x + 1j}) op.compress() with pytest.raises(KeyError) as errorinfo: - term = op[qpsZI] + _ = op[qpsZI] assert "(Zq[0], Iq[1])" in str(errorinfo.value) assert op[qpsXY] == 2 assert re(op[qpsYY]) == 0 diff --git a/pytket/tests/quipper_test.py b/pytket/tests/quipper_test.py index 4247fb660a..f3803f03a5 100644 --- a/pytket/tests/quipper_test.py +++ b/pytket/tests/quipper_test.py @@ -13,14 +13,15 @@ # limitations under the License. from pathlib import Path +from typing import Any +import numpy as np import pytest + from pytket import Circuit from pytket.quipper import circuit_from_quipper from pytket.transform import Transform from pytket.utils.results import compare_unitaries -import numpy as np -from typing import Any curr_file_path = Path(__file__).resolve().parent @@ -40,7 +41,6 @@ def unitary_from_simulate_output(simout: Any, n: int) -> np.ndarray: fmt = "{0:0%db}" % n reps = ["|" + fmt.format(i) + ">" for i in range(N)] lines = simout.split("\n") - n_lines = len(lines) pos = 0 a = np.zeros((N, N), dtype=complex) for i in range(N): @@ -109,6 +109,6 @@ def test_quipper_4() -> None: def test_quipper_5() -> None: # Invalid operation (CCH gate). with pytest.raises(NotImplementedError): - circ = circuit_from_quipper( + _ = circuit_from_quipper( str(curr_file_path / "quipper_test_files" / "test5.quip") ) diff --git a/pytket/tests/simulator/__init__.py b/pytket/tests/simulator/__init__.py index 3067b1fa68..f84e49bb36 100644 --- a/pytket/tests/simulator/__init__.py +++ b/pytket/tests/simulator/__init__.py @@ -12,5 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .tket_sim_backend import TketSimShotBackend, TketSimBackend +from .tket_sim_backend import TketSimBackend, TketSimShotBackend from .tket_sim_wrapper import TketSimWrapper diff --git a/pytket/tests/simulator/tket_sim_backend.py b/pytket/tests/simulator/tket_sim_backend.py index 8824a4d5d2..ca1eaecc8d 100644 --- a/pytket/tests/simulator/tket_sim_backend.py +++ b/pytket/tests/simulator/tket_sim_backend.py @@ -17,26 +17,26 @@ ### WARNING: TketSimBackend accepts Measure gates but does not apply them ### TketSimBackend will not work properly if there are extra bits that are unwritten to. -from .tket_sim_wrapper import TketSimWrapper - -from typing import TYPE_CHECKING, List, Optional, Sequence, Union, cast +from collections.abc import Sequence +from typing import List, Optional, Union, cast from uuid import uuid4 import numpy as np -from pytket.circuit import BasisOrder, Circuit, OpType + from pytket.backends.backend import Backend, KwargTypes from pytket.backends.backend_exceptions import CircuitNotRunError from pytket.backends.backendresult import BackendResult from pytket.backends.resulthandle import ResultHandle, _ResultIdTuple from pytket.backends.status import CircuitStatus, StatusEnum +from pytket.circuit import BasisOrder, Circuit, OpType from pytket.passes import ( + AutoRebase, BasePass, - SequencePass, - SynthesiseTket, - FullPeepholeOptimise, DecomposeBoxes, + FullPeepholeOptimise, + SequencePass, SimplifyInitial, - AutoRebase, + SynthesiseTket, ) from pytket.predicates import ( NoClassicalControlPredicate, @@ -47,6 +47,7 @@ from pytket.utils.outcomearray import OutcomeArray from pytket.utils.results import probs_from_state +from .tket_sim_wrapper import TketSimWrapper _GATE_SET = { OpType.SWAP, @@ -90,7 +91,7 @@ def _result_id_type(self) -> _ResultIdTuple: return (str,) @property - def required_predicates(self) -> List[Predicate]: + def required_predicates(self) -> list[Predicate]: # We don't include a GateSetPredicate; don't list allowed gates. # It's only for testing, so let it raise an exception naturally # if an unknown gate is passed in. @@ -106,22 +107,21 @@ def default_compilation_pass(self, optimisation_level: int = 1) -> BasePass: assert optimisation_level in range(3) if optimisation_level == 0: return SequencePass([DecomposeBoxes(), self.rebase_pass()]) - elif optimisation_level == 1: + if optimisation_level == 1: return SequencePass( [DecomposeBoxes(), SynthesiseTket(), self.rebase_pass()] ) - else: - return SequencePass( - [DecomposeBoxes(), FullPeepholeOptimise(), self.rebase_pass()] - ) + return SequencePass( + [DecomposeBoxes(), FullPeepholeOptimise(), self.rebase_pass()] + ) def process_circuits( self, circuits: Sequence[Circuit], - n_shots: Optional[Union[int, Sequence[int]]] = None, + n_shots: int | Sequence[int] | None = None, valid_check: bool = True, **kwargs: KwargTypes, - ) -> List[ResultHandle]: + ) -> list[ResultHandle]: circuits = list(circuits) if valid_check: self._check_all_circuits(circuits) @@ -157,7 +157,7 @@ def default_compilation_pass(self, optimisation_level: int = 1) -> BasePass: assert optimisation_level in range(3) if optimisation_level == 0: return SequencePass([DecomposeBoxes(), self.rebase_pass()]) - elif optimisation_level == 1: + if optimisation_level == 1: return SequencePass( [ DecomposeBoxes(), @@ -166,32 +166,31 @@ def default_compilation_pass(self, optimisation_level: int = 1) -> BasePass: SimplifyInitial(allow_classical=False, create_all_qubits=True), ] ) - else: - return SequencePass( - [ - DecomposeBoxes(), - FullPeepholeOptimise(), - self.rebase_pass(), - SimplifyInitial(allow_classical=False, create_all_qubits=True), - ] - ) + return SequencePass( + [ + DecomposeBoxes(), + FullPeepholeOptimise(), + self.rebase_pass(), + SimplifyInitial(allow_classical=False, create_all_qubits=True), + ] + ) def process_circuits( self, circuits: Sequence[Circuit], - n_shots: Optional[Union[int, Sequence[int]]] = None, + n_shots: int | Sequence[int] | None = None, valid_check: bool = True, **kwargs: KwargTypes, - ) -> List[ResultHandle]: + ) -> list[ResultHandle]: circuits = list(circuits) - n_shots_list: List[int] = [] + n_shots_list: list[int] = [] if hasattr(n_shots, "__iter__"): - if any(n is None or n < 1 for n in cast(Sequence[Optional[int]], n_shots)): + if any(n is None or n < 1 for n in cast(Sequence[int | None], n_shots)): raise ValueError( "Shots are artificially generated, specify a positive value " "for all list entries of n_shots." ) - n_shots_list = cast(List[int], n_shots) + n_shots_list = cast(list[int], n_shots) if len(n_shots_list) != len(circuits): raise ValueError("The length of n_shots and circuits must match") else: diff --git a/pytket/tests/simulator/tket_sim_wrapper.py b/pytket/tests/simulator/tket_sim_wrapper.py index 20d9351d88..769d60fedd 100644 --- a/pytket/tests/simulator/tket_sim_wrapper.py +++ b/pytket/tests/simulator/tket_sim_wrapper.py @@ -13,7 +13,8 @@ # limitations under the License. import numpy as np -from pytket.circuit import Circuit, BasisOrder + +from pytket.circuit import BasisOrder, Circuit from pytket.utils.results import ( permute_basis_indexing, permute_qubits_in_statevector, @@ -45,5 +46,4 @@ def get_state(self, basis: BasisOrder = BasisOrder.ilo) -> np.ndarray: # tketsim always uses BasisOrder.ilo, so we must convert. rev_perm = tuple(range(self._n_qubits - 1, -1, -1)) statevector = permute_basis_indexing(statevector, rev_perm) - statevector = permute_qubits_in_statevector(statevector, self._qmap_perm) - return statevector + return permute_qubits_in_statevector(statevector, self._qmap_perm) diff --git a/pytket/tests/strategies.py b/pytket/tests/strategies.py index 8975c896a3..71e2ac2d4b 100644 --- a/pytket/tests/strategies.py +++ b/pytket/tests/strategies.py @@ -12,24 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +import re from collections import Counter -import hypothesis.strategies as st -from hypothesis.strategies._internal import SearchStrategy -from hypothesis.extra.numpy import arrays -from typing import Any, Callable +from collections.abc import Callable +from typing import Any +import hypothesis.strategies as st import numpy as np -import re +from hypothesis.extra.numpy import arrays +from hypothesis.strategies._internal import SearchStrategy -from pytket import Circuit, Qubit, Bit -from pytket.circuit import Node, OpType +from pytket import Bit, Circuit, Qubit from pytket.architecture import Architecture +from pytket.backends.backendinfo import BackendInfo +from pytket.backends.backendresult import BackendResult +from pytket.circuit import Node, OpType from pytket.pauli import Pauli, QubitPauliString from pytket.utils import QubitPauliOperator from pytket.utils.outcomearray import OutcomeArray -from pytket.backends.backendresult import BackendResult -from pytket.backends.backendinfo import BackendInfo - binary_digits = st.sampled_from((0, 1)) uint32 = st.integers(min_value=1, max_value=1 << 32 - 1) @@ -131,10 +131,9 @@ def architecture( draw: Callable[[SearchStrategy[Any]], Any], ) -> Architecture: n_nodes = draw(st.integers(min_value=4, max_value=15)) - n_edges = draw(st.integers(min_value=1, max_value=n_nodes)) vertex = st.integers(min_value=0, max_value=n_nodes - 1) edge = st.lists(vertex, min_size=2, max_size=2, unique=True) - edges = st.lists(edge) + edges = st.lists(edge, min_size=1, max_size=n_nodes) return Architecture(draw(edges)) diff --git a/pytket/tests/tableau_test.py b/pytket/tests/tableau_test.py index 1f734f3c8f..cd39c3b331 100644 --- a/pytket/tests/tableau_test.py +++ b/pytket/tests/tableau_test.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest +import numpy as np + from pytket.circuit import Circuit, OpType, Qubit from pytket.pauli import Pauli, QubitPauliTensor -from pytket.tableau import UnitaryTableau, UnitaryRevTableau, UnitaryTableauBox +from pytket.tableau import UnitaryRevTableau, UnitaryTableau, UnitaryTableauBox from pytket.utils.results import compare_unitaries -import numpy as np def test_tableau_box_from_gates() -> None: diff --git a/pytket/tests/tket_sim_test.py b/pytket/tests/tket_sim_test.py index 193d1edcfc..01beb41d0c 100644 --- a/pytket/tests/tket_sim_test.py +++ b/pytket/tests/tket_sim_test.py @@ -12,13 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest -from pytket.circuit import Circuit, CircBox, QControlBox, Op, OpType -from enum import Enum -import numpy as np import math +from enum import Enum from typing import Any, Tuple +import numpy as np +import pytest + +from pytket.circuit import CircBox, Circuit, Op, OpType, QControlBox + # Note: of course, one could write many more (circuit -> unitary) tests. # But the functions are just wrappers around Simulation functions in tket, # which already has quite extensive C++ unit tests. @@ -61,7 +63,7 @@ def append_gates_sequence_1(circ: Circuit) -> None: circ.Rz(0.15, 0) -def get_circuit_triple() -> Tuple[Circuit, Circuit, Circuit]: +def get_circuit_triple() -> tuple[Circuit, Circuit, Circuit]: """Returns 3 circuits, where the final circuit is the first followed by the second.""" c0 = Circuit(2) @@ -104,11 +106,11 @@ def check_that_premultiplication_fails( to see what happens in normal Python code where we pass in objects which are implicitly converted to NumPy objects (or not).""" with pytest.raises(ValueError) as e1: - product = unitary @ matr + _ = unitary @ matr check_matmul_failure_exception_string(str(e1.value)) with pytest.raises(RuntimeError) as e2: - product = circ.get_unitary_times_other(matr) + _ = circ.get_unitary_times_other(matr) message = str(e2) assert "M has wrong number of" in message diff --git a/pytket/tests/transform_test.py b/pytket/tests/transform_test.py index 4edac7b179..4dfc15116a 100644 --- a/pytket/tests/transform_test.py +++ b/pytket/tests/transform_test.py @@ -13,60 +13,55 @@ # limitations under the License. import itertools -from typing import List +import json from pathlib import Path +from typing import List -import sympy +import numpy as np +import pytest +from sympy import Symbol +import pytket.circuit_library as _library +from pytket.architecture import Architecture from pytket.circuit import ( + CircBox, Circuit, + Node, OpType, - CircBox, - Unitary1qBox, PauliExpBox, - Node, Qubit, + Unitary1qBox, ) -import pytket.circuit_library as _library -from pytket.pauli import Pauli +from pytket.circuit.named_types import ParamType +from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager from pytket.passes import ( - RemoveRedundancies, - KAKDecomposition, - SquashCustom, - SquashRzPhasedX, + AutoRebase, + AutoSquash, CommuteThroughMultis, - PauliSquash, - FullPeepholeOptimise, + CustomPass, + CustomRoutingPass, + CXMappingPass, DefaultMappingPass, FullMappingPass, - RoutingPass, - CustomRoutingPass, + FullPeepholeOptimise, + KAKDecomposition, + PauliSquash, PlacementPass, - CXMappingPass, - CustomPass, + RemoveRedundancies, + RoutingPass, SequencePass, SynthesiseTket, - AutoRebase, - AutoSquash, ) -from pytket.predicates import CompilationUnit, NoMidMeasurePredicate -from pytket.transform import Transform, CXConfigType, PauliSynthStrat -from pytket.qasm import circuit_from_qasm -from pytket.architecture import Architecture -from pytket.mapping import MappingManager, LexiRouteRoutingMethod, LexiLabellingMethod +from pytket.pauli import Pauli from pytket.placement import ( - Placement, GraphPlacement, LinePlacement, NoiseAwarePlacement, + Placement, ) - -from sympy import Symbol -import numpy as np -import json -import pytest - -from pytket.circuit.named_types import ParamType +from pytket.predicates import CompilationUnit, NoMidMeasurePredicate +from pytket.qasm import circuit_from_qasm +from pytket.transform import CXConfigType, PauliSynthStrat, Transform def get_test_circuit() -> Circuit: @@ -406,18 +401,14 @@ def test_while_repeat() -> None: c.CX(0, 1) c.Rz(0.34, 0) c.Rx(0.63, 1) - assert ( + assert not ( Transform.while_repeat( Transform.RebaseToCliffordSingles(), Transform.RemoveRedundancies() ).apply(c) - == False - ) - assert ( - Transform.while_repeat( - Transform.CommuteThroughMultis(), Transform.RemoveRedundancies() - ).apply(c) - == True ) + assert Transform.while_repeat( + Transform.CommuteThroughMultis(), Transform.RemoveRedundancies() + ).apply(c) assert c.n_gates_of_type(OpType.CX) == 1 assert c.n_gates_of_type(OpType.Rz) == 0 assert c.n_gates_of_type(OpType.Rx) == 0 @@ -817,7 +808,7 @@ def test_determinism() -> None: def test_full_peephole_optimise() -> None: with open( - Path(__file__).resolve().parent / "json_test_files" / "circuit.json", "r" + Path(__file__).resolve().parent / "json_test_files" / "circuit.json" ) as f: circ = Circuit.from_dict(json.load(f)) @@ -943,8 +934,8 @@ def test_CXMappingPass() -> None: out_circ_1 = cu_1.circuit measure_pred = NoMidMeasurePredicate() - assert measure_pred.verify(cu_0.circuit) == True - assert measure_pred.verify(cu_1.circuit) == False + assert measure_pred.verify(cu_0.circuit) + assert not measure_pred.verify(cu_1.circuit) assert out_circ_0.valid_connectivity(arc, True) assert out_circ_1.valid_connectivity(arc, False) @@ -965,8 +956,8 @@ def test_DefaultMappingPass() -> None: out_circ_0 = cu_0.circuit out_circ_1 = cu_1.circuit measure_pred = NoMidMeasurePredicate() - assert measure_pred.verify(out_circ_0) == True - assert measure_pred.verify(out_circ_1) == False + assert measure_pred.verify(out_circ_0) + assert not measure_pred.verify(out_circ_1) assert out_circ_0.valid_connectivity(arc, False, True) assert out_circ_1.valid_connectivity(arc, False, True) @@ -1161,7 +1152,7 @@ def test_auto_squash() -> None: for gate in itertools.islice(itertools.cycle(gateset), 5): # make a sequence of 5 gates from gateset to make sure squash does # something - params: List[ParamType] = [] + params: list[ParamType] = [] while True: try: circ.add_gate(gate, params, [0]) diff --git a/pytket/tests/unit_id/copy_test.py b/pytket/tests/unit_id/copy_test.py index fa44a96411..df2662235a 100644 --- a/pytket/tests/unit_id/copy_test.py +++ b/pytket/tests/unit_id/copy_test.py @@ -13,7 +13,7 @@ # limitations under the License. from copy import copy, deepcopy -from pytket.unit_id import Bit, Qubit, Node +from pytket.unit_id import Bit, Node, Qubit def test_copying_qubits() -> None: diff --git a/pytket/tests/utils_test.py b/pytket/tests/utils_test.py index 38c3c7bb4c..9eaa183e80 100644 --- a/pytket/tests/utils_test.py +++ b/pytket/tests/utils_test.py @@ -12,44 +12,46 @@ # See the License for the specific language governing permissions and # limitations under the License. +import types from collections import Counter +from collections.abc import Callable +from typing import Any, Dict, List, Tuple + import numpy as np -from pytket.backends.backend import Backend -from hypothesis import strategies, given, settings, HealthCheck +import pytest +from hypothesis import HealthCheck, given, settings, strategies from hypothesis.strategies._internal import SearchStrategy +from simulator import TketSimBackend, TketSimShotBackend # type: ignore +from sympy import symbols -from pytket.circuit import Qubit, Circuit, OpType +from pytket.backends.backend import Backend +from pytket.circuit import Circuit, OpType, Qubit +from pytket.partition import GraphColourMethod, PauliPartitionStrat from pytket.pauli import Pauli, QubitPauliString -from pytket.partition import PauliPartitionStrat, GraphColourMethod from pytket.transform import Transform +from pytket.utils import Graph, QubitPauliOperator from pytket.utils.expectations import ( - expectation_from_shots, expectation_from_counts, + expectation_from_shots, get_operator_expectation_value, get_pauli_expectation_value, ) -from pytket.utils.measurements import append_pauli_measurement, _all_pauli_measurements +from pytket.utils.measurements import _all_pauli_measurements, append_pauli_measurement +from pytket.utils.outcomearray import OutcomeArray from pytket.utils.results import ( + compare_statevectors, counts_from_shot_table, get_n_qb_from_statevector, - probs_from_counts, - probs_from_state, int_dist_from_state, permute_basis_indexing, - compare_statevectors, + probs_from_counts, + probs_from_state, ) -from pytket.utils.outcomearray import OutcomeArray -from pytket.utils import QubitPauliOperator, Graph +from pytket.utils.stats import gate_counts from pytket.utils.symbolic import ( circuit_apply_symbolic_statevector, circuit_to_symbolic_unitary, ) -from pytket.utils.stats import gate_counts -import pytest -import types -from sympy import symbols -from typing import Any, Callable, Tuple, Dict, List -from simulator import TketSimShotBackend, TketSimBackend # type: ignore def test_append_measurements() -> None: @@ -97,7 +99,7 @@ def test_shots_to_counts() -> None: def test_counts_to_probs() -> None: - counts: Dict[Tuple[int, ...], int] = {(0, 0): 4, (0, 1): 1, (1, 1): 3} + counts: dict[tuple[int, ...], int] = {(0, 0): 4, (0, 1): 1, (1, 1): 3} probs = probs_from_counts(counts) assert len(probs) == 3 assert probs[(0, 0)] == 0.5 @@ -186,7 +188,7 @@ def test_shot_expectation() -> None: def test_count_expectation() -> None: - counts: Dict[Tuple[int, ...], int] = { + counts: dict[tuple[int, ...], int] = { (0, 0, 1): 4, (0, 1, 0): 7, (1, 0, 0): 1, @@ -248,7 +250,7 @@ def test_outcomearray() -> None: assert counts1D[outcomeA] == 1 # 0 width outcomearrays - readouts: List[List[int]] = [[] for _ in range(10)] + readouts: list[list[int]] = [[] for _ in range(10)] empty_array = OutcomeArray.from_readouts(readouts) assert np.array_equal(empty_array.to_readouts(), readouts) assert empty_array.counts() == Counter( diff --git a/pytket/tests/zx_diagram_test.py b/pytket/tests/zx_diagram_test.py index 839928f6fd..ac8e7aebea 100644 --- a/pytket/tests/zx_diagram_test.py +++ b/pytket/tests/zx_diagram_test.py @@ -12,41 +12,44 @@ # See the License for the specific language governing permissions and # limitations under the License. -from math import pow, isclose +from math import isclose, pow +from typing import Tuple + import numpy as np import pytest -from pytket import Qubit, Circuit, OpType +from sympy import sympify + +from pytket import Circuit, OpType, Qubit from pytket.passes import AutoRebase from pytket.pauli import Pauli, QubitPauliString from pytket.utils.results import compare_unitaries from pytket.zx import ( + CliffordGen, + DirectedGen, + PhasedGen, + QuantumType, + Rewrite, + ZXBox, ZXDiagram, + ZXGen, ZXType, - QuantumType, ZXWireType, - ZXGen, - Rewrite, circuit_to_zx, - PhasedGen, - CliffordGen, - DirectedGen, - ZXBox, ) -from sympy import sympify -from typing import Tuple have_quimb: bool = True try: import quimb.tensor # type: ignore + from pytket.zx.tensor_eval import ( - unitary_from_quantum_diagram, + density_matrix_from_cptp_diagram, fix_boundaries_to_binary_states, fix_inputs_to_binary_state, fix_outputs_to_binary_state, - tensor_from_quantum_diagram, tensor_from_mixed_diagram, + tensor_from_quantum_diagram, unitary_from_classical_diagram, - density_matrix_from_cptp_diagram, + unitary_from_quantum_diagram, ) except ModuleNotFoundError: have_quimb = False @@ -770,7 +773,7 @@ def test_constructors() -> None: phased_gen = PhasedGen(ZXType.ZSpider, 0.5, QuantumType.Quantum) assert phased_gen.param == 0.5 clifford_gen = CliffordGen(ZXType.PX, True, QuantumType.Quantum) - assert clifford_gen.param == True + assert clifford_gen.param directed_gen = DirectedGen(ZXType.Triangle, QuantumType.Quantum) assert directed_gen.signature == [QuantumType.Quantum] * 2 diag = ZXDiagram(4, 4, 0, 0) @@ -780,9 +783,9 @@ def test_constructors() -> None: def joint_normalise_tensor( a: np.ndarray, b: np.ndarray -) -> Tuple[np.ndarray, np.ndarray]: - a_linear = a.reshape((a.size)) - b_linear = b.reshape((b.size)) +) -> tuple[np.ndarray, np.ndarray]: + a_linear = a.reshape(a.size) + b_linear = b.reshape(b.size) max_i = 0 max_val = 0 for i in range(a.size): diff --git a/schemas/circuit_v1.json b/schemas/circuit_v1.json index c801393fbd..d788e573d3 100644 --- a/schemas/circuit_v1.json +++ b/schemas/circuit_v1.json @@ -302,6 +302,9 @@ }, "classical": { "$ref": "#/definitions/classical" + }, + "expr": { + "$ref": "#/definitions/wiredclexpr" } }, "required": [ @@ -357,6 +360,20 @@ "classical" ] } + }, + { + "if": { + "properties": { + "type": { + "const": "ClExpr" + } + } + }, + "then": { + "required": [ + "expr" + ] + } } ] }, @@ -1178,6 +1195,147 @@ "qubits" ], "additionalProperties": false + }, + "wiredclexpr": { + "type": "object", + "description": "A classical operation defined over a sequence of bits.", + "properties": { + "expr": { + "$ref": "#/definitions/clexpr" + }, + "bit_posn": { + "type": "array", + "description": "List of pairs representing map from bit-variable indices to wire positions.", + "items": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "reg_posn": { + "type": "array", + "description": "List of pairs representing map from register-variable indices to lists of wire positions.", + "items": { + "type": "array", + "items": [ + { + "type": "integer" + }, + { + "type": "array", + "items": { + "type": "integer" + } + } + ] + } + }, + "output_posn": { + "type": "array", + "description": "List of wire positions of output bits.", + "items": { + "type": "integer" + } + } + }, + "required": [ + "expr", + "bit_posn", + "reg_posn", + "output_posn" + ], + "additionalProperties": false + }, + "clexpr": { + "type": "object", + "description": "An abstract classical expression.", + "properties": { + "op": { + "description": "The operation type.", + "type": "string" + }, + "args": { + "type": "array", + "description": "List of arguments to the operation.", + "items": { + "$ref": "#/definitions/clexprarg" + } + } + } + }, + "clexprarg":{ + "type": "object", + "description": "Argument to a classical expression.", + "properties": { + "type": { + "description": "The type of argument.", + "type": "string", + "enum": [ + "term", + "expr" + ] + }, + "input": { + "anyOf": [ + { + "$ref": "#/definitions/clexprterm" + }, + { + "$ref": "#/definitions/clexpr" + } + ] + } + } + }, + "clexprterm": { + "type": "object", + "description": "A term in a classical expression.", + "properties": { + "type": { + "description": "The type of term.", + "type": "string", + "enum": [ + "int", + "var" + ] + }, + "term": { + "anyOf": [ + { + "type": "integer" + }, + { + "$ref": "#/definitions/clvar" + } + ] + } + } + }, + "clvar": { + "type": "object", + "description": "A free variable in a classical expression.", + "properties": { + "type": { + "description": "The type of variable.", + "type": "string", + "enum": [ + "bit", + "ref" + ] + }, + "var": { + "$ref": "#/definitions/clindex" + } + } + }, + "clindex": { + "type": "object", + "properties": { + "index": { + "type": "integer" + } + } } } } diff --git a/schemas/compiler_pass_v1.json b/schemas/compiler_pass_v1.json index b8175c8b92..3a3dd7d85f 100644 --- a/schemas/compiler_pass_v1.json +++ b/schemas/compiler_pass_v1.json @@ -373,6 +373,18 @@ "allow_zzphase": { "type": "boolean", "definition": "parameter controlling the use of ZZPhase gates in \"GreedyPauliSimp\"" + }, + "thread_timeout": { + "type": "number", + "definition": "parameter controlling the maximum runtime of a single thread in \"GreedyPauliSimp\"" + }, + "only_reduce": { + "type": "boolean", + "definition": "parameter controlling whether \"GreedyPauliSimp\" can return circuits with more two qubit gates" + }, + "trials": { + "type": "number", + "definition": "parameter controlling the number of random solutions found when calling \"GreedyPauliSimp\"" } }, "required": [ @@ -644,9 +656,10 @@ }, "then": { "required": [ - "label" + "label", + "relabel_classical_registers" ], - "maxProperties": 2 + "maxProperties": 3 } }, { @@ -903,9 +916,12 @@ "max_lookahead", "max_tqe_candidates", "seed", - "allow_zzphase" + "allow_zzphase", + "thread_timeout", + "only_reduce", + "trials" ], - "maxProperties": 7 + "maxProperties": 10 } }, { diff --git a/tket/conanfile.py b/tket/conanfile.py index 6439e45b7a..fa46ab76f8 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.3.35" + version = "1.3.48" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" @@ -114,7 +114,7 @@ def requirements(self): self.requires("boost/1.86.0", transitive_headers=True) self.requires("eigen/3.4.0", transitive_headers=True) self.requires("nlohmann_json/3.11.3", transitive_headers=True) - self.requires("symengine/0.12.0", transitive_headers=True) + self.requires("symengine/0.13.0", transitive_headers=True) self.requires("tkassert/0.3.4@tket/stable", transitive_headers=True) self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") diff --git a/tket/include/tket/Circuit/Boxes.hpp b/tket/include/tket/Circuit/Boxes.hpp index af2d6a859a..68a4b874b8 100644 --- a/tket/include/tket/Circuit/Boxes.hpp +++ b/tket/include/tket/Circuit/Boxes.hpp @@ -494,6 +494,10 @@ class CustomGate : public Box { bool is_clifford() const override; + Op_ptr dagger() const override; + + Op_ptr transpose() const override; + protected: void generate_circuit() const override; CustomGate() : Box(OpType::CustomGate), gate_(), params_() {} diff --git a/tket/include/tket/Circuit/Circuit.hpp b/tket/include/tket/Circuit/Circuit.hpp index 64a4f338c9..606c2c3b0f 100644 --- a/tket/include/tket/Circuit/Circuit.hpp +++ b/tket/include/tket/Circuit/Circuit.hpp @@ -749,9 +749,12 @@ class Circuit { /** * Convert all quantum and classical bits to use default registers. * + * @param relabel_classical_expression Whether expressions in ClassicalExpBox + * have their expr updated to match the input wires + * * @return mapping from old to new unit IDs */ - unit_map_t flatten_registers(); + unit_map_t flatten_registers(bool relabel_classical_expression = true); //_________________________________________________ @@ -1044,7 +1047,8 @@ class Circuit { * @return true iff circuit was modified */ template - bool rename_units(const std::map &qm); + bool rename_units( + const std::map &qm, bool relabel_classicalexpbox = true); /** Automatically rewire holes when removing vertices from the circuit? */ enum class GraphRewiring { Yes, No }; @@ -1118,7 +1122,8 @@ class Circuit { */ std::map op_counts() const; - unsigned count_gates(const OpType &op_type) const; + unsigned count_gates( + const OpType &op_type, const bool include_conditional = false) const; VertexSet get_gates_of_type(const OpType &op_type) const; /** @@ -1718,7 +1723,8 @@ JSON_DECL(Circuit) /** Templated method definitions */ template -bool Circuit::rename_units(const std::map &qm) { +bool Circuit::rename_units( + const std::map &qm, bool relabel_classicalexpbox) { // Can only work for Unit classes static_assert(std::is_base_of::value); static_assert(std::is_base_of::value); @@ -1767,21 +1773,18 @@ bool Circuit::rename_units(const std::map &qm) { "Unit already exists in circuit: " + pair.first.repr()); TKET_ASSERT(modified); } - // For every ClassicalExpBox, update its logic expressions - if (!bm.empty()) { + if (!bm.empty() && relabel_classicalexpbox) { BGL_FORALL_VERTICES(v, dag, DAG) { Op_ptr op = get_Op_ptr_from_Vertex(v); if (op->get_type() == OpType::ClassicalExpBox) { const ClassicalExpBoxBase &cbox = static_cast(*op); // rename_units is marked as const to get around the Op_ptr - // cast, but it can still mutate a python object modified |= cbox.rename_units(bm); } } } - return modified; } diff --git a/tket/include/tket/Ops/ClExpr.hpp b/tket/include/tket/Ops/ClExpr.hpp index 1adf73d696..712aad2af6 100644 --- a/tket/include/tket/Ops/ClExpr.hpp +++ b/tket/include/tket/Ops/ClExpr.hpp @@ -19,6 +19,7 @@ * @brief Classical expressions involving bits and registers */ +#include #include #include #include @@ -124,7 +125,7 @@ void from_json(const nlohmann::json& j, ClExprVar& var); /** * A term in a classical expression (either a constant or a variable) */ -typedef std::variant ClExprTerm; +typedef std::variant ClExprTerm; std::ostream& operator<<(std::ostream& os, const ClExprTerm& term); diff --git a/tket/include/tket/Predicates/CompilerPass.hpp b/tket/include/tket/Predicates/CompilerPass.hpp index b40e6206e8..f04750f97f 100644 --- a/tket/include/tket/Predicates/CompilerPass.hpp +++ b/tket/include/tket/Predicates/CompilerPass.hpp @@ -33,8 +33,6 @@ typedef std::pair PassConditions; typedef std::function PassCallback; -JSON_DECL(PassPtr) - class IncompatibleCompilerPasses : public std::logic_error { public: explicit IncompatibleCompilerPasses(const std::type_index& typeid1) @@ -301,6 +299,14 @@ class RepeatUntilSatisfiedPass : public BasePass { PredicatePtr pred_; }; +nlohmann::json serialise(const BasePass& bp); +nlohmann::json serialise(const PassPtr& pp); +nlohmann::json serialise(const std::vector& pp); + +PassPtr deserialise( + const nlohmann::json& j, + const std::map>& + custom_deserialise = {}); // TODO: Repeat with a metric, repeat until a Predicate is satisfied... } // namespace tket diff --git a/tket/include/tket/Predicates/PassGenerators.hpp b/tket/include/tket/Predicates/PassGenerators.hpp index 9e7f87931c..65861a4659 100644 --- a/tket/include/tket/Predicates/PassGenerators.hpp +++ b/tket/include/tket/Predicates/PassGenerators.hpp @@ -110,7 +110,8 @@ PassPtr gen_clifford_push_through_pass(); * Qubits removed from the Circuit are preserved in the bimap, but not updated * to a new labelling. */ -PassPtr gen_flatten_relabel_registers_pass(const std::string& label); +PassPtr gen_flatten_relabel_registers_pass( + const std::string& label, bool relabel_classical_expressions = true); /** * Pass to rename some or all qubits according to the given map. * @@ -354,12 +355,17 @@ PassPtr gen_special_UCC_synthesis( * @param max_tqe_candidates * @param seed * @param allow_zzphase + * @param thread_timeout + * @param only_reduce + * @param trials * @return PassPtr */ PassPtr gen_greedy_pauli_simp( double discount_rate = 0.7, double depth_weight = 0.3, unsigned max_lookahead = 500, unsigned max_tqe_candidates = 500, - unsigned seed = 0, bool allow_zzphase = false); + unsigned seed = 0, bool allow_zzphase = false, + unsigned thread_timeout = 100, bool only_reduce = false, + unsigned trials = 1); /** * Generate a pass to simplify the circuit where it acts on known basis states. diff --git a/tket/include/tket/Transformations/GreedyPauliOptimisation.hpp b/tket/include/tket/Transformations/GreedyPauliOptimisation.hpp index de6bb19d5c..8f53b08bde 100644 --- a/tket/include/tket/Transformations/GreedyPauliOptimisation.hpp +++ b/tket/include/tket/Transformations/GreedyPauliOptimisation.hpp @@ -14,6 +14,8 @@ #pragma once +#include + #include "Transform.hpp" #include "tket/Circuit/Circuit.hpp" #include "tket/Clifford/UnitaryTableau.hpp" @@ -600,6 +602,29 @@ class GPGraph { std::tuple, std::vector> gpg_from_unordered_set(const std::vector& unordered_set); +/** + * @brief Converts the given circuit into a GPGraph and conjugates each node + * by greedily applying 2-qubit Clifford gates until the node can be realised + * as a single-qubit gate, a measurement, or a reset. The final Clifford + * operator is synthesized in a similar fashion. Allows early termination + * from a thread via a stop_flag. + * + * @param circ + * @param stop_flag + * @param discount_rate + * @param depth_weight + * @param max_lookahead + * @param max_tqe_candidates + * @param seed + * @param allow_zzphase + * @return Circuit + */ +Circuit greedy_pauli_graph_synthesis_flag( + Circuit circ, std::atomic& stop_flag, double discount_rate = 0.7, + double depth_weight = 0.3, unsigned max_lookahead = 500, + unsigned max_tqe_candidates = 500, unsigned seed = 0, + bool allow_zzphase = false); + /** * @brief Converts the given circuit into a GPGraph and conjugates each node * by greedily applying 2-qubit Clifford gates until the node can be realised @@ -643,7 +668,8 @@ Circuit greedy_pauli_set_synthesis( Transform greedy_pauli_optimisation( double discount_rate = 0.7, double depth_weight = 0.3, unsigned max_lookahead = 500, unsigned max_tqe_candidates = 500, - unsigned seed = 0, bool allow_zzphase = false); + unsigned seed = 0, bool allow_zzphase = false, + unsigned thread_timeout = 100, unsigned trials = 1); } // namespace Transforms diff --git a/tket/src/Circuit/Boxes.cpp b/tket/src/Circuit/Boxes.cpp index 40d00fb6b8..4679fc79c7 100644 --- a/tket/src/Circuit/Boxes.cpp +++ b/tket/src/Circuit/Boxes.cpp @@ -372,6 +372,22 @@ bool CustomGate::is_clifford() const { return true; } +Op_ptr CustomGate::dagger() const { + Circuit inner_c_dag = gate_->get_def()->dagger(); + composite_def_ptr_t dag_def_ptr = std::make_shared( + gate_->get_name() + "_dagger", gate_->get_def()->dagger(), + gate_->get_args()); + return std::make_shared(dag_def_ptr, params_); +} + +Op_ptr CustomGate::transpose() const { + Circuit inner_c_dag = gate_->get_def()->transpose(); + composite_def_ptr_t dag_def_ptr = std::make_shared( + gate_->get_name() + "_transpose", gate_->get_def()->transpose(), + gate_->get_args()); + return std::make_shared(dag_def_ptr, params_); +} + QControlBox::QControlBox( const Op_ptr &op, unsigned n_controls, const std::vector &control_state) diff --git a/tket/src/Circuit/CircPool.cpp b/tket/src/Circuit/CircPool.cpp index 0a99a34f4e..3a5dc98e0d 100644 --- a/tket/src/Circuit/CircPool.cpp +++ b/tket/src/Circuit/CircPool.cpp @@ -1306,18 +1306,6 @@ static Circuit _tk1_to_rzsx( c.add_op(OpType::SX, {0}); c.add_op(OpType::Rz, alpha, {0}); correction_phase = int_half(beta - 0.5) - 0.25; - } else if (equiv_0(beta + 0.5) && equiv_0(alpha) && equiv_0(gamma)) { - // a = 2k, b = 2m-0.5, c = 2n - // Rz(2k)Rx(2m - 0.5)Rz(2n) = (-1)^{k+m+n}e^{i \pi /4} X.SX - if (allow_x) { - c.add_op(OpType::X, {0}); - } else { - c.add_op(OpType::SX, {0}); - c.add_op(OpType::SX, {0}); - } - c.add_op(OpType::SX, {0}); - correction_phase = - int_half(beta + 0.5) + int_half(alpha) + int_half(gamma) + 0.25; } else if (equiv_0(beta + 0.5)) { // SX.Rz(2m+0.5).SX = (-1)^{m}e^{i \pi /4} Rz(0.5).SX.Rz(0.5) c.add_op(OpType::Rz, gamma + 1, {0}); diff --git a/tket/src/Circuit/Circuit.cpp b/tket/src/Circuit/Circuit.cpp index e85292b2b6..115880b746 100644 --- a/tket/src/Circuit/Circuit.cpp +++ b/tket/src/Circuit/Circuit.cpp @@ -177,7 +177,7 @@ void Circuit::symbol_substitution(const SymEngine::map_basic_basic sub_map) { BGL_FORALL_VERTICES(v, dag, DAG) { Op_ptr new_op = get_Op_ptr_from_Vertex(v)->symbol_substitution(sub_map); if (new_op) { - dag[v] = {new_op}; + dag[v] = {new_op, dag[v].opgroup}; } } phase = phase.subs(sub_map); diff --git a/tket/src/Circuit/basic_circ_manip.cpp b/tket/src/Circuit/basic_circ_manip.cpp index ca3d27faa3..9bb8b11902 100644 --- a/tket/src/Circuit/basic_circ_manip.cpp +++ b/tket/src/Circuit/basic_circ_manip.cpp @@ -422,7 +422,7 @@ void Circuit::remove_edge(const Edge& edge) { boost::remove_edge(edge, this->dag); } -unit_map_t Circuit::flatten_registers() { +unit_map_t Circuit::flatten_registers(bool relabel_classical_expression) { unit_map_t rename_map; unsigned q_index = 0; unsigned c_index = 0; @@ -434,7 +434,7 @@ unit_map_t Circuit::flatten_registers() { } } try { - rename_units(rename_map); + rename_units(rename_map, relabel_classical_expression); } catch (const std::exception& e) { std::stringstream ss; ss << "Unable to flatten registers: " << e.what(); diff --git a/tket/src/Circuit/macro_circ_info.cpp b/tket/src/Circuit/macro_circ_info.cpp index 5f6e2bf9f6..73fc0a3b31 100644 --- a/tket/src/Circuit/macro_circ_info.cpp +++ b/tket/src/Circuit/macro_circ_info.cpp @@ -51,11 +51,19 @@ std::map Circuit::op_counts() const { return counts; } -unsigned Circuit::count_gates(const OpType& op_type) const { +unsigned Circuit::count_gates( + const OpType& op_type, const bool include_conditional) const { unsigned counter = 0; BGL_FORALL_VERTICES(v, dag, DAG) { if (get_OpType_from_Vertex(v) == op_type) { ++counter; + } else if ( + include_conditional && + (get_OpType_from_Vertex(v) == OpType::Conditional) && + (static_cast(*get_Op_ptr_from_Vertex(v)) + .get_op() + ->get_type() == op_type)) { + ++counter; } } return counter; diff --git a/tket/src/OpType/OpTypeFunctions.cpp b/tket/src/OpType/OpTypeFunctions.cpp index 0c2a0c98a6..1b6cdadf44 100644 --- a/tket/src/OpType/OpTypeFunctions.cpp +++ b/tket/src/OpType/OpTypeFunctions.cpp @@ -254,10 +254,9 @@ bool is_oneway_type(OpType optype) { // or we do not yet have the dagger gate as an OpType. // If the gate can have an dagger, define it in the dagger() method. static const OpTypeSet no_defined_inverse = { - OpType::Input, OpType::Output, OpType::Measure, - OpType::ClInput, OpType::ClOutput, OpType::Barrier, - OpType::Reset, OpType::Collapse, OpType::CustomGate, - OpType::PhasePolyBox, OpType::Create, OpType::Discard}; + OpType::Input, OpType::Output, OpType::Measure, OpType::ClInput, + OpType::ClOutput, OpType::Barrier, OpType::Reset, OpType::Collapse, + OpType::PhasePolyBox, OpType::Create, OpType::Discard}; return find_in_set(optype, no_defined_inverse); } diff --git a/tket/src/Ops/ClExpr.cpp b/tket/src/Ops/ClExpr.cpp index 0e0f1fdd5a..880bbd986d 100644 --- a/tket/src/Ops/ClExpr.cpp +++ b/tket/src/Ops/ClExpr.cpp @@ -15,6 +15,7 @@ #include "tket/Ops/ClExpr.hpp" #include +#include #include #include #include @@ -131,7 +132,7 @@ void from_json(const nlohmann::json& j, ClExprVar& var) { } std::ostream& operator<<(std::ostream& os, const ClExprTerm& term) { - if (const int* n = std::get_if(&term)) { + if (const uint64_t* n = std::get_if(&term)) { return os << *n; } else { ClExprVar var = std::get(term); @@ -141,7 +142,7 @@ std::ostream& operator<<(std::ostream& os, const ClExprTerm& term) { void to_json(nlohmann::json& j, const ClExprTerm& term) { nlohmann::json inner_j; - if (const int* n = std::get_if(&term)) { + if (const uint64_t* n = std::get_if(&term)) { j["type"] = "int"; inner_j = *n; } else { @@ -155,7 +156,7 @@ void to_json(nlohmann::json& j, const ClExprTerm& term) { void from_json(const nlohmann::json& j, ClExprTerm& term) { const std::string termtype = j.at("type").get(); if (termtype == "int") { - term = j.at("term").get(); + term = j.at("term").get(); } else { TKET_ASSERT(termtype == "var"); term = j.at("term").get(); diff --git a/tket/src/Predicates/CompilerPass.cpp b/tket/src/Predicates/CompilerPass.cpp index 43691b0faa..e09262fcc7 100644 --- a/tket/src/Predicates/CompilerPass.cpp +++ b/tket/src/Predicates/CompilerPass.cpp @@ -251,7 +251,7 @@ std::string SequencePass::to_string() const { nlohmann::json SequencePass::get_config() const { nlohmann::json j; j["pass_class"] = "SequencePass"; - j["SequencePass"]["sequence"] = seq_; + j["SequencePass"]["sequence"] = serialise(seq_); return j; } @@ -270,7 +270,7 @@ std::string RepeatPass::to_string() const { nlohmann::json RepeatPass::get_config() const { nlohmann::json j; j["pass_class"] = "RepeatPass"; - j["RepeatPass"]["body"] = pass_; + j["RepeatPass"]["body"] = serialise(pass_); return j; } @@ -313,7 +313,7 @@ std::string RepeatWithMetricPass::to_string() const { nlohmann::json RepeatWithMetricPass::get_config() const { nlohmann::json j; j["pass_class"] = "RepeatWithMetricPass"; - j["RepeatWithMetricPass"]["body"] = pass_; + j["RepeatWithMetricPass"]["body"] = serialise(pass_); j["RepeatWithMetricPass"]["metric"] = "SERIALIZATION OF METRICS NOT YET IMPLEMENTED"; return j; @@ -347,15 +347,27 @@ std::string RepeatUntilSatisfiedPass::to_string() const { nlohmann::json RepeatUntilSatisfiedPass::get_config() const { nlohmann::json j; j["pass_class"] = "RepeatUntilSatisfiedPass"; - j["RepeatUntilSatisfiedPass"]["body"] = pass_; + j["RepeatUntilSatisfiedPass"]["body"] = serialise(pass_); j["RepeatUntilSatisfiedPass"]["predicate"] = pred_; return j; } -void to_json(nlohmann::json& j, const PassPtr& pp) { j = pp->get_config(); } +nlohmann::json serialise(const BasePass& bp) { return bp.get_config(); } +nlohmann::json serialise(const PassPtr& pp) { return pp->get_config(); } +nlohmann::json serialise(const std::vector& pp) { + nlohmann::json j = nlohmann::json::array(); + for (const auto& p : pp) { + j.push_back(serialise(p)); + } + return j; +} -void from_json(const nlohmann::json& j, PassPtr& pp) { +PassPtr deserialise( + const nlohmann::json& j, + const std::map>& + custom_deserialise) { std::string classname = j.at("pass_class").get(); + PassPtr pp; if (classname == "StandardPass") { const nlohmann::json& content = j.at("StandardPass"); std::string passname = content.at("name").get(); @@ -457,7 +469,8 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { pp = gen_euler_pass(q, p, s); } else if (passname == "FlattenRelabelRegistersPass") { pp = gen_flatten_relabel_registers_pass( - content.at("label").get()); + content.at("label").get(), + content.at("relabel_classical_expressions").get()); } else if (passname == "RoutingPass") { Architecture arc = content.at("architecture").get(); std::vector con = content.at("routing_config"); @@ -509,9 +522,12 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { unsigned max_lookahead = content.at("max_lookahead").get(); unsigned seed = content.at("seed").get(); bool allow_zzphase = content.at("allow_zzphase").get(); + unsigned timeout = content.at("thread_timeout").get(); + bool only_reduce = content.at("only_reduce").get(); + unsigned trials = content.at("trials").get(); pp = gen_greedy_pauli_simp( discount_rate, depth_weight, max_lookahead, max_tqe_candidates, seed, - allow_zzphase); + allow_zzphase, timeout, only_reduce, trials); } else if (passname == "PauliSimp") { // SEQUENCE PASS - DESERIALIZABLE ONLY @@ -576,6 +592,17 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { unsigned n = content.at("n").get(); bool only_zeros = content.at("only_zeros").get(); pp = RoundAngles(n, only_zeros); + } else if (passname == "CustomPass") { + std::string label = content.at("label").get(); + auto it = custom_deserialise.find(label); + if (it != custom_deserialise.end()) { + pp = CustomPass(it->second, label); + } else { + throw JsonError( + "Cannot deserialise CustomPass without passing a " + "custom_deserialisation map " + "with a key corresponding to the pass's label."); + } } else { throw JsonError("Cannot load StandardPass of unknown type"); } @@ -583,22 +610,24 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { const nlohmann::json& content = j.at("SequencePass"); std::vector seq; for (const auto& j_entry : content.at("sequence")) { - seq.push_back(j_entry.get()); + seq.push_back(deserialise(j_entry, custom_deserialise)); } pp = std::make_shared(seq); } else if (classname == "RepeatPass") { const nlohmann::json& content = j.at("RepeatPass"); - pp = std::make_shared(content.at("body").get()); + pp = std::make_shared( + deserialise(content.at("body"), custom_deserialise)); } else if (classname == "RepeatWithMetricPass") { throw PassNotSerializable(classname); } else if (classname == "RepeatUntilSatisfiedPass") { const nlohmann::json& content = j.at("RepeatUntilSatisfiedPass"); - PassPtr body = content.at("body").get(); + PassPtr body = deserialise(content.at("body"), custom_deserialise); PredicatePtr pred = content.at("predicate").get(); pp = std::make_shared(body, pred); } else { throw JsonError("Cannot load PassPtr of unknown type."); } + return pp; } } // namespace tket diff --git a/tket/src/Predicates/PassGenerators.cpp b/tket/src/Predicates/PassGenerators.cpp index 610caabd84..88766bbb2e 100644 --- a/tket/src/Predicates/PassGenerators.cpp +++ b/tket/src/Predicates/PassGenerators.cpp @@ -244,7 +244,6 @@ PassPtr gen_auto_rebase_pass(const OpTypeSet& allowed_gates, bool allow_swaps) { PredicatePtrMap precons; OpTypeSet all_types(allowed_gates); all_types.insert(OpType::Measure); - all_types.insert(OpType::Collapse); all_types.insert(OpType::Reset); PredicatePtr postcon1 = std::make_shared(all_types); PredicatePtr postcon2 = std::make_shared(); @@ -354,7 +353,8 @@ PassPtr gen_clifford_push_through_pass() { return std::make_shared(precons, t, pc, j); } -PassPtr gen_flatten_relabel_registers_pass(const std::string& label) { +PassPtr gen_flatten_relabel_registers_pass( + const std::string& label, bool relabel_classical_expressions) { Transform t = Transform([=](Circuit& circuit, std::shared_ptr maps) { unsigned n_qubits = circuit.n_qubits(); @@ -366,7 +366,7 @@ PassPtr gen_flatten_relabel_registers_pass(const std::string& label) { relabelling_map.insert({all_qubits[i], Qubit(label, i)}); } - circuit.rename_units(relabelling_map); + circuit.rename_units(relabelling_map, relabel_classical_expressions); changed |= update_maps(maps, relabelling_map, relabelling_map); return changed; }); @@ -376,6 +376,7 @@ PassPtr gen_flatten_relabel_registers_pass(const std::string& label) { nlohmann::json j; j["name"] = "FlattenRelabelRegistersPass"; j["label"] = label; + j["relabel_classical_expressions"] = relabel_classical_expressions; return std::make_shared(precons, t, postcons, j); } @@ -1015,10 +1016,34 @@ PassPtr gen_synthesise_pauli_graph( PassPtr gen_greedy_pauli_simp( double discount_rate, double depth_weight, unsigned max_lookahead, - unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase) { - Transform t = Transforms::greedy_pauli_optimisation( - discount_rate, depth_weight, max_lookahead, max_tqe_candidates, seed, - allow_zzphase); + unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase, + unsigned thread_timeout, bool only_reduce, unsigned trials) { + Transform t = Transform([discount_rate, depth_weight, max_lookahead, + max_tqe_candidates, seed, allow_zzphase, + thread_timeout, only_reduce, trials](Circuit& circ) { + Transform gpo = Transforms::greedy_pauli_optimisation( + discount_rate, depth_weight, max_lookahead, max_tqe_candidates, seed, + allow_zzphase, thread_timeout, trials); + if (only_reduce) { + Circuit gpo_circ = circ; + // comparison will be inaccurate if circuit has PauliExpBox + gpo_circ.decompose_boxes_recursively(); + unsigned original_n_2qb_gates = gpo_circ.count_n_qubit_gates(2); + unsigned original_n_gates = gpo_circ.n_gates(); + unsigned original_depth = gpo_circ.depth(); + if (gpo.apply(gpo_circ) && + std::make_tuple( + gpo_circ.count_n_qubit_gates(2), gpo_circ.n_gates(), + gpo_circ.depth()) < + std::make_tuple( + original_n_2qb_gates, original_n_gates, original_depth)) { + circ = gpo_circ; + return true; + } + return false; + } + return gpo.apply(circ); + }); OpTypeSet ins = { OpType::Z, OpType::X, @@ -1067,6 +1092,9 @@ PassPtr gen_greedy_pauli_simp( j["max_tqe_candidates"] = max_tqe_candidates; j["seed"] = seed; j["allow_zzphase"] = allow_zzphase; + j["thread_timeout"] = thread_timeout; + j["only_reduce"] = only_reduce; + j["trials"] = trials; return std::make_shared(precons, t, postcon, j); } diff --git a/tket/src/Transformations/GreedyPauliOptimisation.cpp b/tket/src/Transformations/GreedyPauliOptimisation.cpp index e052102656..3c80ee1eed 100644 --- a/tket/src/Transformations/GreedyPauliOptimisation.cpp +++ b/tket/src/Transformations/GreedyPauliOptimisation.cpp @@ -15,6 +15,9 @@ #include "tket/Transformations/GreedyPauliOptimisation.hpp" #include +#include +#include +#include #include #include "tket/Circuit/PauliExpBoxes.hpp" @@ -325,7 +328,8 @@ struct DepthTracker { static void tableau_row_nodes_synthesis( std::vector& rows, Circuit& circ, DepthTracker& depth_tracker, double depth_weight, unsigned max_lookahead, - unsigned max_tqe_candidates, unsigned seed) { + unsigned max_tqe_candidates, unsigned seed, + std::shared_ptr> stop_flag) { // only consider nodes with a non-zero cost std::vector remaining_indices; for (unsigned i = 0; i < rows.size(); i++) { @@ -334,6 +338,10 @@ static void tableau_row_nodes_synthesis( } } while (remaining_indices.size() != 0) { + // check early termination + if (stop_flag.get()->load()) { + return; + }; // get nodes with min cost std::vector min_nodes_indices = {remaining_indices[0]}; unsigned min_cost = rows[remaining_indices[0]]->tqe_cost(); @@ -605,11 +613,18 @@ static void pauli_exps_synthesis( std::vector& rows, Circuit& circ, DepthTracker& depth_tracker, double discount_rate, double depth_weight, unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, - bool allow_zzphase) { + bool allow_zzphase, std::shared_ptr> stop_flag) { while (true) { + // check timeout + if (stop_flag.get()->load()) { + return; + }; + consume_nodes( rotation_sets, circ, depth_tracker, discount_rate, depth_weight); + if (rotation_sets.empty()) break; + std::vector& first_set = rotation_sets[0]; // get nodes with min cost std::vector min_nodes_indices = {0}; @@ -632,6 +647,7 @@ static void pauli_exps_synthesis( // sample std::vector sampled_tqes = sample_tqes(tqe_candidates, max_tqe_candidates, seed); + // for each tqe we compute costs which might subject to normalisation std::map> tqe_candidates_cost; for (const TQE& tqe : sampled_tqes) { @@ -719,28 +735,29 @@ Circuit greedy_pauli_set_synthesis( std::vector> rotation_sets{rotation_set}; DepthTracker depth_tracker(n_qubits); // synthesise Pauli exps + std::shared_ptr> dummy_stop_flag = + std::make_shared>(false); pauli_exps_synthesis( rotation_sets, rows, c, depth_tracker, 0, depth_weight, max_lookahead, - max_tqe_candidates, seed, allow_zzphase); + max_tqe_candidates, seed, allow_zzphase, dummy_stop_flag); // synthesise the tableau tableau_row_nodes_synthesis( rows, c, depth_tracker, depth_weight, max_lookahead, max_tqe_candidates, - seed); + seed, dummy_stop_flag); c.replace_SWAPs(); return c; } -Circuit greedy_pauli_graph_synthesis( - const Circuit& circ, double discount_rate, double depth_weight, - unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, - bool allow_zzphase) { +Circuit greedy_pauli_graph_synthesis_flag( + Circuit circ, std::shared_ptr> stop_flag, + double discount_rate, double depth_weight, unsigned max_lookahead, + unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase) { if (max_lookahead == 0) { throw GreedyPauliSimpError("max_lookahead must be greater than 0."); } if (max_tqe_candidates == 0) { throw GreedyPauliSimpError("max_tqe_candidates must be greater than 0."); } - Circuit circ_flat(circ); unsigned n_qubits = circ_flat.n_qubits(); unsigned n_bits = circ_flat.n_bits(); @@ -750,42 +767,112 @@ Circuit greedy_pauli_graph_synthesis( if (name != std::nullopt) { new_circ.set_name(name.value()); } - unit_map_t unit_map = circ_flat.flatten_registers(); + unit_map_t unit_map = circ_flat.flatten_registers(false); unit_map_t rev_unit_map; for (const auto& pair : unit_map) { rev_unit_map.insert({pair.second, pair.first}); } GPGraph gpg(circ_flat); + + // We regularly check whether the timeout has ocurred + if (stop_flag.get()->load()) { + return Circuit(); + } + auto [rotation_sets, rows, measures] = gpg.get_sequence(); + + if (stop_flag.get()->load()) { + return Circuit(); + } + DepthTracker depth_tracker(n_qubits); // synthesise Pauli exps pauli_exps_synthesis( rotation_sets, rows, new_circ, depth_tracker, discount_rate, depth_weight, - max_lookahead, max_tqe_candidates, seed, allow_zzphase); + max_lookahead, max_tqe_candidates, seed, allow_zzphase, stop_flag); + + if (stop_flag.get()->load()) { + return Circuit(); + } // synthesise the tableau tableau_row_nodes_synthesis( rows, new_circ, depth_tracker, depth_weight, max_lookahead, - max_tqe_candidates, seed); + max_tqe_candidates, seed, stop_flag); + + if (stop_flag.get()->load()) { + return Circuit(); + } + for (auto it = measures.begin(); it != measures.end(); ++it) { new_circ.add_measure(it->left, it->right); } - new_circ.rename_units(rev_unit_map); + new_circ.rename_units(rev_unit_map, false); new_circ.replace_SWAPs(); return new_circ; } +Circuit greedy_pauli_graph_synthesis( + const Circuit& circ, double discount_rate, double depth_weight, + unsigned max_lookahead, unsigned max_tqe_candidates, unsigned seed, + bool allow_zzphase) { + std::shared_ptr> dummy_stop_flag = + std::make_shared>(false); + return greedy_pauli_graph_synthesis_flag( + circ, dummy_stop_flag, discount_rate, depth_weight, max_lookahead, + max_tqe_candidates, seed, allow_zzphase); +} + } // namespace GreedyPauliSimp Transform greedy_pauli_optimisation( double discount_rate, double depth_weight, unsigned max_lookahead, - unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase) { + unsigned max_tqe_candidates, unsigned seed, bool allow_zzphase, + unsigned thread_timeout, unsigned trials) { return Transform([discount_rate, depth_weight, max_lookahead, - max_tqe_candidates, seed, allow_zzphase](Circuit& circ) { - circ = GreedyPauliSimp::greedy_pauli_graph_synthesis( - circ, discount_rate, depth_weight, max_lookahead, max_tqe_candidates, - seed, allow_zzphase); - // decompose the conditional CircBoxes - circ.decompose_boxes_recursively(); + max_tqe_candidates, seed, allow_zzphase, thread_timeout, + trials](Circuit& circ) { + std::mt19937 seed_gen(seed); + std::vector circuits; + unsigned threads_started = 0; + + while (threads_started < trials) { + std::shared_ptr> stop_flag = + std::make_shared>(false); + std::future future = std::async( + std::launch::async, + [&, stop_flag]() { // Capture `stop_flag` explicitly in the lambda + return GreedyPauliSimp::greedy_pauli_graph_synthesis_flag( + circ, stop_flag, discount_rate, depth_weight, max_lookahead, + max_tqe_candidates, seed_gen(), allow_zzphase); + }); + threads_started++; + + if (future.wait_for(std::chrono::seconds(thread_timeout)) == + std::future_status::ready) { + Circuit c = future.get(); + c.decompose_boxes_recursively(); + circuits.push_back(c); + } else { + // If the thread isn't complete within time, prompt cancelling the + // optimisation and break from while loop + *stop_flag = true; + break; + } + } + + // Return the smallest circuit if any were found within the single + // thread_timeout + // If none are found then return false + if (circuits.empty()) return false; + auto min = std::min_element( + circuits.begin(), circuits.end(), + [](const Circuit& a, const Circuit& b) { + return std::make_tuple( + a.count_n_qubit_gates(2), a.n_gates(), a.depth()) < + std::make_tuple( + b.count_n_qubit_gates(2), b.n_gates(), b.depth()); + }); + circ = *min; return true; }); } diff --git a/tket/test/src/Circuit/test_Circ.cpp b/tket/test/src/Circuit/test_Circ.cpp index 0575e382c7..2f47486862 100644 --- a/tket/test/src/Circuit/test_Circ.cpp +++ b/tket/test/src/Circuit/test_Circ.cpp @@ -22,6 +22,7 @@ #include "../testutil.hpp" #include "tket/Circuit/Circuit.hpp" #include "tket/Circuit/DAGDefs.hpp" +#include "tket/Circuit/PauliExpBoxes.hpp" #include "tket/Circuit/Simulation/CircuitSimulator.hpp" #include "tket/Gate/GatePtr.hpp" #include "tket/Gate/OpPtrFunctions.hpp" @@ -175,6 +176,25 @@ SCENARIO( } } +SCENARIO("test conditional count") { + GIVEN("conditional circ") { + Circuit circ; + register_t qreg = circ.add_q_register("qb", 2); + register_t creg = circ.add_c_register("b", 2); + circ.add_conditional_gate(OpType::H, {}, {qreg[1]}, {creg[0]}, 1); + circ.add_conditional_gate(OpType::H, {}, {qreg[1]}, {creg[0]}, 1); + circ.add_conditional_gate(OpType::H, {}, {qreg[1]}, {creg[0]}, 1); + circ.add_op(OpType::H, {Qubit(qreg[0])}); + circ.add_op(OpType::H, {Qubit(qreg[0])}); + REQUIRE(circ.n_qubits() == 2); + REQUIRE(circ.n_bits() == 2); + REQUIRE(circ.count_gates(OpType::CX, false) == 0); + REQUIRE(circ.count_gates(OpType::H, false) == 2); + REQUIRE(circ.count_gates(OpType::CX, true) == 0); + REQUIRE(circ.count_gates(OpType::H, true) == 5); + } +} + SCENARIO("Creating gates via Qubits and Registers") { GIVEN("A purely quantum circuit") { Circuit circ; @@ -1292,6 +1312,36 @@ SCENARIO("Functions with symbolic ops") { REQUIRE(test_equiv_val(op2->get_params()[0], 0.2)); REQUIRE(op3->get_type() == OpType::Barrier); } + GIVEN("A circuit with symbolic gates and boxes that belong to opgroups.") { + Sym asym = SymEngine::symbol("a"); + Expr alpha(asym); + Sym bsym = SymEngine::symbol("b"); + Expr beta(bsym); + Circuit circ(2); + circ.add_op(OpType::Rx, alpha, {0}, "Rx"); + Circuit inner_circ(2); + inner_circ.add_op(OpType::Rx, {alpha}, {0}); + inner_circ.add_op(OpType::Ry, {beta}, {0}); + auto cbox = CircBox(inner_circ); + circ.add_box(cbox, {0, 1}, "cbox"); + DensePauliMap paulis0{Pauli::X, Pauli::X}; + DensePauliMap paulis1{Pauli::Z, Pauli::X}; + auto ppbox = PauliExpPairBox( + SymPauliTensor(paulis0, alpha), SymPauliTensor(paulis1, beta)); + circ.add_box(ppbox, {0, 1}, "ppbox"); + symbol_map_t symbol_map; + symbol_map[asym] = Expr(0.2); + symbol_map[bsym] = Expr(0.3); + circ.symbol_substitution(symbol_map); + REQUIRE(!circ.is_symbolic()); + std::unordered_set opgroups({"Rx", "cbox", "ppbox"}); + REQUIRE(circ.get_opgroups() == opgroups); + std::vector cmds = circ.get_commands(); + REQUIRE(cmds.size() == 3); + REQUIRE(cmds[0].get_opgroup().value() == "Rx"); + REQUIRE(cmds[1].get_opgroup().value() == "cbox"); + REQUIRE(cmds[2].get_opgroup().value() == "ppbox"); + } } SCENARIO("Test depth_by_type method") { diff --git a/tket/test/src/test_ClExpr.cpp b/tket/test/src/test_ClExpr.cpp index a49888ce5e..248a8cfbbe 100644 --- a/tket/test/src/test_ClExpr.cpp +++ b/tket/test/src/test_ClExpr.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include #include @@ -127,7 +128,7 @@ SCENARIO("Serialization and stringification") { REQUIRE(var_reg1 == var_reg); } GIVEN("ClExprTerm") { - ClExprTerm term_int = 7; + ClExprTerm term_int = uint64_t{7}; ClExprTerm term_var = ClRegVar{5}; std::stringstream ss; ss << term_int << ", " << term_var; @@ -140,14 +141,14 @@ SCENARIO("Serialization and stringification") { REQUIRE(term_var1 == term_var); } GIVEN("Vector of ClExprArg (1)") { - std::vector args{ClRegVar{2}, int{3}}; + std::vector args{ClRegVar{2}, uint64_t{3}}; nlohmann::json j = args; std::vector args1 = j.get>(); REQUIRE(args == args1); } GIVEN("ClExpr (1)") { // r0 + 7 - ClExpr expr(ClOp::RegAdd, {ClRegVar{0}, int{7}}); + ClExpr expr(ClOp::RegAdd, {ClRegVar{0}, uint64_t{7}}); std::stringstream ss; ss << expr; REQUIRE(ss.str() == "add(r0, 7)"); @@ -156,7 +157,7 @@ SCENARIO("Serialization and stringification") { REQUIRE(expr1 == expr); } GIVEN("Vector of ClExprArg (2)") { - ClExpr expr(ClOp::RegAdd, {ClRegVar{0}, int{8}}); + ClExpr expr(ClOp::RegAdd, {ClRegVar{0}, uint64_t{8}}); std::vector args{expr}; nlohmann::json j = args; std::vector args1 = j.get>(); @@ -165,7 +166,7 @@ SCENARIO("Serialization and stringification") { GIVEN("ClExpr (2)") { // (r0 + r1) / (r2 * 3) ClExpr numer(ClOp::RegAdd, {ClRegVar{0}, ClRegVar{1}}); - ClExpr denom(ClOp::RegMul, {ClRegVar{2}, int{3}}); + ClExpr denom(ClOp::RegMul, {ClRegVar{2}, uint64_t{3}}); ClExpr expr(ClOp::RegDiv, {numer, denom}); std::stringstream ss; ss << expr; diff --git a/tket/test/src/test_GreedyPauli.cpp b/tket/test/src/test_GreedyPauli.cpp index 366143ece4..76e0c0cfc6 100644 --- a/tket/test/src/test_GreedyPauli.cpp +++ b/tket/test/src/test_GreedyPauli.cpp @@ -22,6 +22,7 @@ #include "tket/Gate/SymTable.hpp" #include "tket/Ops/ClassicalOps.hpp" #include "tket/Predicates/PassGenerators.hpp" +#include "tket/Predicates/PassLibrary.hpp" #include "tket/Transformations/GreedyPauliOptimisation.hpp" #include "tket/Utils/Expression.hpp" @@ -619,9 +620,38 @@ SCENARIO("Complete synthesis") { PauliExpBox( SymPauliTensor({Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, 0.125)), {1, 3, 5, 0}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::X}, + 0.125)), + {1, 3, 5, 0, 2, 4}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, + 0.125)), + {0, 1, 2, 3, 4, 5}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, + 0.125)), + {5, 2, 4, 1, 3, 0}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::X}, + 0.125)), + {0, 5, 1, 4, 3, 2}); + Circuit d(circ); - REQUIRE(Transforms::greedy_pauli_optimisation(0.7, 0.3, 500, 500, 0, true) - .apply(d)); + REQUIRE( + !Transforms::greedy_pauli_optimisation(0.7, 0.3, 500, 500, 0, true, 0) + .apply(d)); + REQUIRE( + Transforms::greedy_pauli_optimisation(0.7, 0.3, 500, 500, 0, true, 10) + .apply(d)); REQUIRE(test_unitary_comparison(circ, d, true)); } GIVEN("Select TQE over ZZPhase") { @@ -709,6 +739,7 @@ SCENARIO("Test GreedyPauliSimp for individual gates") { REQUIRE(test_unitary_comparison(circ, d, true)); } } + SCENARIO("Test GreedyPauliSimp pass construction") { // test pass construction GIVEN("A circuit") { @@ -719,6 +750,84 @@ SCENARIO("Test GreedyPauliSimp pass construction") { CHECK(gen_greedy_pauli_simp(0.3, 0.5)->apply(cu)); REQUIRE(test_unitary_comparison(c, cu.get_circ_ref(), true)); } + GIVEN("Preserving a circuit with only_reduce set to true.") { + Circuit c(4); + c.add_op(OpType::CX, {0, 1}); + c.add_op(OpType::CX, {1, 2}); + c.add_op(OpType::CX, {2, 3}); + CompilationUnit cu(c); + REQUIRE(!gen_greedy_pauli_simp(0.3, 0.5, 500, 500, 0, false, 100, true) + ->apply(cu)); + } +} + +SCENARIO("Test GreedyPauliSimp with multiple trials and threads") { + GIVEN("Large circuit with ZZPhase") { + Circuit circ(6); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::X}, 0.3)), {0, 1}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Z, Pauli::Y, Pauli::X}, 0.2)), + {0, 1, 2}); + circ.add_box( + PauliExpCommutingSetBox({ + {{Pauli::I, Pauli::Y, Pauli::I, Pauli::Z}, 1.2}, + {{Pauli::X, Pauli::Y, Pauli::Z, Pauli::I}, 0.8}, + {{Pauli::I, Pauli::I, Pauli::I, Pauli::Z}, 1.25}, + }), + {1, 2, 3, 4}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Y, Pauli::X}, 0.1)), {2, 3}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Z, Pauli::Y, Pauli::X}, 0.11)), + {1, 3, 4}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Y, Pauli::Y}, 0.2)), {4, 5}); + circ.add_box( + PauliExpBox(SymPauliTensor({Pauli::Z, Pauli::Z, Pauli::X}, 0.15)), + {2, 4, 5}); + circ.add_box( + PauliExpBox( + SymPauliTensor({Pauli::X, Pauli::X, Pauli::X, Pauli::X}, 0.25)), + {2, 4, 5, 0}); + circ.add_box( + PauliExpBox( + SymPauliTensor({Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, 0.125)), + {1, 3, 5, 0}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::X}, + 0.125)), + {1, 3, 5, 0, 2, 4}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, + 0.125)), + {0, 1, 2, 3, 4, 5}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, + 0.125)), + {5, 2, 4, 1, 3, 0}); + + circ.add_box( + PauliExpBox(SymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::X}, + 0.125)), + {0, 5, 1, 4, 3, 2}); + + Circuit d(circ); + REQUIRE(!Transforms::greedy_pauli_optimisation( + 0.7, 0.3, 500, 500, 0, true, 0, 10) + .apply(d)); + REQUIRE(Transforms::greedy_pauli_optimisation( + 0.7, 0.3, 500, 500, 0, true, 10, 10) + .apply(d)); + REQUIRE(test_unitary_comparison(circ, d, true)); + } } } // namespace test_GreedyPauliSimp } // namespace tket diff --git a/tket/test/src/test_json.cpp b/tket/test/src/test_json.cpp index d2843b6a57..e0e2704709 100644 --- a/tket/test/src/test_json.cpp +++ b/tket/test/src/test_json.cpp @@ -901,12 +901,12 @@ SCENARIO("Test compiler pass serializations") { CompilationUnit cu{circ}; \ CompilationUnit copy = cu; \ PassPtr pp = pass; \ - nlohmann::json j_pp = pp; \ - PassPtr loaded = j_pp.get(); \ + nlohmann::json j_pp = serialise(pp); \ + PassPtr loaded = deserialise(j_pp); \ pp->apply(cu); \ loaded->apply(copy); \ REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); \ - nlohmann::json j_loaded = loaded; \ + nlohmann::json j_loaded = serialise(loaded); \ REQUIRE(j_pp == j_loaded); \ } COMPPASSJSONTEST(CommuteThroughMultis, CommuteThroughMultis()) @@ -986,14 +986,14 @@ SCENARIO("Test compiler pass serializations") { CompilationUnit copy = cu; PassPtr pp = gen_pauli_exponentials( Transforms::PauliSynthStrat::Sets, CXConfigType::Tree); - nlohmann::json j_pp = pp; - PassPtr loaded = j_pp.get(); + nlohmann::json j_pp = serialise(pp); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); DecomposeBoxes()->apply(cu); DecomposeBoxes()->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); - nlohmann::json j_loaded = loaded; + nlohmann::json j_loaded = serialise(loaded); REQUIRE(j_pp == j_loaded); } GIVEN("RoutingPass") { @@ -1004,12 +1004,12 @@ SCENARIO("Test compiler pass serializations") { placement->apply(cu); CompilationUnit copy = cu; PassPtr pp = gen_routing_pass(arc, rcon); - nlohmann::json j_pp = pp; - PassPtr loaded = j_pp.get(); + nlohmann::json j_pp = serialise(pp); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); - nlohmann::json j_loaded = loaded; + nlohmann::json j_loaded = serialise(loaded); REQUIRE(j_pp == j_loaded); } GIVEN("Routing with multiple routing methods") { @@ -1023,12 +1023,12 @@ SCENARIO("Test compiler pass serializations") { placement->apply(cu); CompilationUnit copy = cu; PassPtr pp = gen_routing_pass(arc, mrcon); - nlohmann::json j_pp = pp; - PassPtr loaded = j_pp.get(); + nlohmann::json j_pp = serialise(pp); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); - nlohmann::json j_loaded = loaded; + nlohmann::json j_loaded = serialise(loaded); REQUIRE(j_pp == j_loaded); } GIVEN("FullMappingPass") { @@ -1049,7 +1049,7 @@ SCENARIO("Test compiler pass serializations") { } j_pp["StandardPass"]["routing_config"] = config_array; - PassPtr loaded = j_pp.get(); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); @@ -1065,7 +1065,7 @@ SCENARIO("Test compiler pass serializations") { j_pp["StandardPass"]["name"] = "DefaultMappingPass"; j_pp["StandardPass"]["architecture"] = arc; j_pp["StandardPass"]["delay_measures"] = true; - PassPtr loaded = j_pp.get(); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); @@ -1088,7 +1088,7 @@ SCENARIO("Test compiler pass serializations") { j_pp["StandardPass"]["routing_config"] = config_array; j_pp["StandardPass"]["directed"] = true; j_pp["StandardPass"]["delay_measures"] = false; - PassPtr loaded = j_pp.get(); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); @@ -1106,7 +1106,7 @@ SCENARIO("Test compiler pass serializations") { j_pp["StandardPass"]["pauli_synth_strat"] = Transforms::PauliSynthStrat::Sets; j_pp["StandardPass"]["cx_config"] = CXConfigType::Star; - PassPtr loaded = j_pp.get(); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); @@ -1124,7 +1124,7 @@ SCENARIO("Test compiler pass serializations") { j_pp["StandardPass"]["pauli_synth_strat"] = Transforms::PauliSynthStrat::Sets; j_pp["StandardPass"]["cx_config"] = CXConfigType::Star; - PassPtr loaded = j_pp.get(); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); @@ -1143,7 +1143,7 @@ SCENARIO("Test compiler pass serializations") { j_pp["StandardPass"]["name"] = "ContextSimp"; j_pp["StandardPass"]["allow_classical"] = true; j_pp["StandardPass"]["x_circuit"] = CircPool::X(); - PassPtr loaded = j_pp.get(); + PassPtr loaded = deserialise(j_pp); pp->apply(cu); loaded->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); @@ -1158,12 +1158,12 @@ SCENARIO("Test compiler pass combinator serializations") { std::vector seq_vec = { gen_pauli_exponentials(), DecomposeBoxes(), gen_clifford_simp_pass()}; PassPtr seq = std::make_shared(seq_vec); - nlohmann::json j_seq = seq; - PassPtr loaded_seq = j_seq.get(); + nlohmann::json j_seq = serialise(seq); + PassPtr loaded_seq = deserialise(j_seq); seq->apply(cu); loaded_seq->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); - nlohmann::json j_loaded_seq = loaded_seq; + nlohmann::json j_loaded_seq = serialise(loaded_seq); REQUIRE(j_seq == j_loaded_seq); } GIVEN("A complex pass with multiple combinators") { @@ -1186,12 +1186,12 @@ SCENARIO("Test compiler pass combinator serializations") { PassPtr rep = std::make_shared(seq, gate_set); PassPtr comb = std::make_shared(std::vector{rep, RebaseTket()}); - nlohmann::json j_comb = comb; - PassPtr loaded_comb = j_comb.get(); + nlohmann::json j_comb = serialise(comb); + PassPtr loaded_comb = deserialise(j_comb); comb->apply(cu); loaded_comb->apply(copy); REQUIRE(cu.get_circ_ref() == copy.get_circ_ref()); - nlohmann::json j_loaded_comb = loaded_comb; + nlohmann::json j_loaded_comb = serialise(loaded_comb); REQUIRE(j_comb == j_loaded_comb); } }