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] 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)") {