diff --git a/.github/workflows/build-with-nix.yml b/.github/workflows/build-with-nix.yml index f8c977c3dd..08ac988e19 100644 --- a/.github/workflows/build-with-nix.yml +++ b/.github/workflows/build-with-nix.yml @@ -11,10 +11,14 @@ jobs: build_and_test: strategy: matrix: - os: ['ubuntu-22.04', 'macos-13-xlarge'] + os: ['ubuntu-22.04', 'macos-14'] runs-on: ${{matrix.os}} steps: - uses: actions/checkout@v4 - uses: cachix/install-nix-action@v26 + - uses: cachix/cachix-action@v14 + with: + name: tket + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - name: Build and test tket run: nix flake check -L diff --git a/.github/workflows/build-without-conan.yml b/.github/workflows/build-without-conan.yml index 7807b620d8..1454e4d032 100644 --- a/.github/workflows/build-without-conan.yml +++ b/.github/workflows/build-without-conan.yml @@ -74,9 +74,9 @@ jobs: - name: Install catch2 run: | cd ${TMP_DIR} - wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.5.2.tar.gz - tar xzvf v3.5.2.tar.gz - cd Catch2-3.5.2/ + wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.5.4.tar.gz + tar xzvf v3.5.4.tar.gz + cd Catch2-3.5.4/ mkdir build cd build cmake -GNinja -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} .. @@ -96,9 +96,9 @@ jobs: - name: Install pybind11 run: | cd ${TMP_DIR} - wget https://github.com/pybind/pybind11/archive/refs/tags/v2.11.1.tar.gz - tar xzvf v2.11.1.tar.gz - cd pybind11-2.11.1/ + wget https://github.com/pybind/pybind11/archive/refs/tags/v2.12.0.tar.gz + tar xzvf v2.12.0.tar.gz + cd pybind11-2.12.0/ mkdir build cd build cmake -GNinja -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DPYBIND11_TEST=OFF .. diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index a976740029..9df57f11f7 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -97,7 +97,7 @@ jobs: if: needs.check_changes.outputs.tket_or_workflow_changed == 'true' strategy: matrix: - os: ['ubuntu-22.04', 'macos-12', 'macos-13-xlarge'] + os: ['ubuntu-22.04', 'macos-12', 'macos-14'] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -330,7 +330,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-13-xlarge'] + os: ['macos-12', 'macos-14'] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -401,10 +401,9 @@ 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-13-xlarge' && (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') + if: matrix.os == 'macos-14' && (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') run: | - python -m pip install -U mypy - python -m pip install pybind11-stubgen==2.3.6 # https://github.com/CQCL/tket/issues/1135 + python -m pip install -U mypy pybind11-stubgen cd pytket ./stub_generation/regenerate_stubs git diff --quiet pytket/_tket && echo "Stubs are up-to-date" || exit 1 # fail if stubs change after regeneration diff --git a/.github/workflows/build_libs.yml b/.github/workflows/build_libs.yml index 592c3e4dc9..735058d515 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-22.04', 'macos-12', 'windows-2022', 'macos-13-xlarge'] + os: ['ubuntu-22.04', 'macos-12', 'windows-2022', 'macos-14'] 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 35b72ef1bb..d1b91ea09e 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -17,7 +17,7 @@ jobs: name: Build strategy: matrix: - os: ['ubuntu-22.04', 'macos-12', 'windows-2022', 'macos-13-xlarge'] + os: ['ubuntu-22.04', 'macos-12', 'windows-2022', 'macos-14'] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d247b1f44b..269c9b0d28 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,8 +62,8 @@ jobs: name: Linux_aarch64_3.${{ matrix.python3-version }}_wheel path: wheelhouse/ - build_macos_wheels: - name: Build macos wheels + build_macos_x86_wheels: + name: Build macos x86 wheels runs-on: macos-12 strategy: matrix: @@ -101,12 +101,12 @@ jobs: delocate-wheel -v -w "$GITHUB_WORKSPACE/wheelhouse/" "dist/pytket-"*".whl" - uses: actions/upload-artifact@v4 with: - name: MacOS_${{ matrix.python-version }}_wheel + name: MacOS_x86_${{ matrix.python-version }}_wheel path: wheelhouse/ - build_macos_M1_wheels: - name: Build macos (M1) wheels - runs-on: macos-13-xlarge + build_macos_arm64_wheels: + name: Build macos arm64 wheels + runs-on: macos-14 strategy: matrix: python-version: ['3.10', '3.11', '3.12'] @@ -125,7 +125,7 @@ jobs: run: | conan profile detect DEFAULT_PROFILE_PATH=`conan profile path default` - PROFILE_PATH=./conan-profiles/macos-13-xlarge + PROFILE_PATH=./conan-profiles/macos-14 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,12 +138,13 @@ jobs: cd pytket # Ensure wheels are compatible with MacOS 12.0 and later: export WHEEL_PLAT_NAME=macosx_12_0_arm64 - python${{ matrix.python-version }} -m pip install -U pip build delocate + python${{ matrix.python-version }} -m pip install -U pip build + python${{ matrix.python-version }} -m pip install delocate~=0.10.7 python${{ matrix.python-version }} -m build delocate-wheel -v -w "$GITHUB_WORKSPACE/wheelhouse/" "dist/pytket-"*".whl" - uses: actions/upload-artifact@v4 with: - name: MacOS_M1_${{ matrix.python-version }}_wheel + name: MacOS_arm64_${{ matrix.python-version }}_wheel path: wheelhouse/ build_Windows_wheels: @@ -252,9 +253,9 @@ jobs: - name: Run tests run: cd tket/pytket/tests && pytest --ignore=simulator/ - test_macos_wheels: - name: Test macos wheels - needs: build_macos_wheels + test_macos_x86_wheels: + name: Test macos x86 wheels + needs: build_macos_x86_wheels strategy: matrix: os: ['macos-12', 'macos-13'] @@ -268,7 +269,7 @@ jobs: - name: Download wheels uses: actions/download-artifact@v4 with: - name: MacOS_${{ matrix.python-version }}_wheel + name: MacOS_x86_${{ matrix.python-version }}_wheel path: wheelhouse/ - uses: actions/checkout@v4 with: @@ -282,13 +283,14 @@ jobs: pip install -r requirements.txt pytest --ignore=simulator/ - test_macos_M1_wheels: - name: Test macos (M1) wheels - needs: build_macos_M1_wheels - runs-on: macos-13-xlarge + test_macos_arm64_wheels: + name: Test macos arm64 wheels + needs: build_macos_arm64_wheels strategy: matrix: + os: ['macos-13-xlarge', 'macos-14'] python-version: ['3.10', '3.11', '3.12'] + runs-on: ${{ matrix.os }} steps: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 @@ -297,7 +299,7 @@ jobs: - name: Download wheels uses: actions/download-artifact@v4 with: - name: MacOS_M1_${{ matrix.python-version }}_wheel + name: MacOS_arm64_${{ matrix.python-version }}_wheel path: wheelhouse/ - uses: actions/checkout@v4 with: @@ -344,7 +346,7 @@ jobs: publish_to_pypi: name: Publish to pypi if: github.event_name == 'release' - needs: [test_linux_wheels, test_linux_aarch64_wheels, test_macos_wheels, test_macos_M1_wheels, test_Windows_wheels] + needs: [test_linux_wheels, test_linux_aarch64_wheels, test_macos_x86_wheels, test_macos_arm64_wheels, test_Windows_wheels] runs-on: ubuntu-22.04 steps: - name: Download all wheels diff --git a/.github/workflows/test_libs.yml b/.github/workflows/test_libs.yml index 6a1226efe3..abe72ad419 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-22.04', 'macos-12', 'windows-2022', 'macos-13-xlarge'] + os: ['ubuntu-22.04', 'macos-12', 'windows-2022', 'macos-14'] 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 ac4aa9efff..3421d87374 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-22.04', 'macos-12', 'windows-2022', 'macos-13-xlarge'] + os: ['ubuntu-22.04', 'macos-12', 'windows-2022', 'macos-14'] lib: ['tklog', 'tkassert', 'tkrng', 'tktokenswap', 'tkwsm'] runs-on: ${{ matrix.os }} steps: diff --git a/.gitignore b/.gitignore index 48491ab516..f034b4ca4c 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ pytket/tests/qasm_test_files/testout6.qasm #nix result +.envrc +.direnv/ diff --git a/README.md b/README.md index cfbe2ec40f..d00fb29554 100644 --- a/README.md +++ b/README.md @@ -153,3 +153,13 @@ building and testing pytket. Tket and pytket are available as a Nix flake, with support for Linux and Apple Silicon systems. See the [README](nix-support/README.md) in the `nix-support` directory for instructions on building and testing tket and pytket through Nix, and on how to use it within a Nix project. + +To launch into a tket environment, you can use + +``` +nix develop github:CQCL/tket +``` + +We use Cachix to cache pre-built artifacts, which provides a faster install time for nix users. +To make use of this cache, enable our cachix substituter with `cachix use tket`, or enter a +tket nix environment from a trusted user and confirm the use of the tket.cachix.org substituter. diff --git a/build-without-conan.md b/build-without-conan.md index e756bec4fb..5a40b5d2c4 100644 --- a/build-without-conan.md +++ b/build-without-conan.md @@ -91,9 +91,9 @@ cmake --install . ``` cd ${TMP_DIR} -wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.5.2.tar.gz -tar xzvf v3.5.2.tar.gz -cd Catch2-3.5.2/ +wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.5.4.tar.gz +tar xzvf v3.5.4.tar.gz +cd Catch2-3.5.4/ mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} .. @@ -119,9 +119,9 @@ cmake --install . ``` cd ${TMP_DIR} -wget https://github.com/pybind/pybind11/archive/refs/tags/v2.11.1.tar.gz -tar xzvf v2.11.1.tar.gz -cd pybind11-2.11.1/ +wget https://github.com/pybind/pybind11/archive/refs/tags/v2.12.0.tar.gz +tar xzvf v2.12.0.tar.gz +cd pybind11-2.12.0/ mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DPYBIND11_TEST=OFF .. diff --git a/conan-profiles/macos-13-xlarge b/conan-profiles/macos-14 similarity index 85% rename from conan-profiles/macos-13-xlarge rename to conan-profiles/macos-14 index 8f73dd19c5..38919cc4de 100644 --- a/conan-profiles/macos-13-xlarge +++ b/conan-profiles/macos-14 @@ -4,5 +4,5 @@ build_type=Release compiler=apple-clang compiler.cppstd=gnu17 compiler.libcxx=libc++ -compiler.version=14 +compiler.version=15 os=Macos diff --git a/conan-profiles/macos-armv8-clang14 b/conan-profiles/macos-armv8-clang14 deleted file mode 100644 index 8f73dd19c5..0000000000 --- a/conan-profiles/macos-armv8-clang14 +++ /dev/null @@ -1,8 +0,0 @@ -[settings] -arch=armv8 -build_type=Release -compiler=apple-clang -compiler.cppstd=gnu17 -compiler.libcxx=libc++ -compiler.version=14 -os=Macos diff --git a/flake.lock b/flake.lock index 6f2399adf4..d91efff9c4 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1700484102, - "narHash": "sha256-yGPHhYBH8KZu70gZvHRBvIzN0Z9dlwXX7u3hFFiGevA=", + "lastModified": 1712940319, + "narHash": "sha256-QN9FhrpcHQLfc6CJmgNOD2vXz9/aGTEqDYTWtdc/mi0=", "owner": "nixos", "repo": "nixpkgs", - "rev": "48a753219ede79d448cd0aa3bbf29d29fcbab8c9", + "rev": "623ac957cb99a5647c9cf127ed6b5b9edfbba087", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index a82c8cf73d..44dddc80e0 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,10 @@ { description = "Tket Quantum SDK"; + nixConfig.extra-substituters = "https://tket.cachix.org https://cache.nixos.org"; + nixConfig.trusted-public-keys = '' + tket.cachix.org-1:ACdm5Zg19qPL0PpvUwTPPiIx8SEUy+D/uqa9vKJFwh0= + cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= + ''; inputs.nixpkgs.url = "github:nixos/nixpkgs"; inputs.flake-utils.url = "github:numtide/flake-utils"; outputs = { self, nixpkgs, flake-utils }: diff --git a/libs/tkassert/test/conanfile.py b/libs/tkassert/test/conanfile.py index 498c921f81..c3fc9af371 100644 --- a/libs/tkassert/test/conanfile.py +++ b/libs/tkassert/test/conanfile.py @@ -60,4 +60,4 @@ def package(self): def requirements(self): self.requires("tkassert/0.3.4") - self.requires("catch2/3.5.2") + self.requires("catch2/3.5.4") diff --git a/libs/tklog/test/conanfile.py b/libs/tklog/test/conanfile.py index 9d1596c24b..b250a0bbbd 100644 --- a/libs/tklog/test/conanfile.py +++ b/libs/tklog/test/conanfile.py @@ -60,4 +60,4 @@ def package(self): def requirements(self): self.requires("tklog/0.3.3") - self.requires("catch2/3.5.2") + self.requires("catch2/3.5.4") diff --git a/libs/tkrng/test/conanfile.py b/libs/tkrng/test/conanfile.py index 025d26376b..eea5bec9f4 100644 --- a/libs/tkrng/test/conanfile.py +++ b/libs/tkrng/test/conanfile.py @@ -60,4 +60,4 @@ def package(self): def requirements(self): self.requires("tkrng/0.3.3") - self.requires("catch2/3.5.2") + self.requires("catch2/3.5.4") diff --git a/libs/tktokenswap/test/conanfile.py b/libs/tktokenswap/test/conanfile.py index 8923967b3f..ec9cdaf165 100644 --- a/libs/tktokenswap/test/conanfile.py +++ b/libs/tktokenswap/test/conanfile.py @@ -61,4 +61,4 @@ def package(self): def requirements(self): self.requires("tktokenswap/0.3.7") self.requires("tkrng/0.3.3@tket/stable") - self.requires("catch2/3.5.2") + self.requires("catch2/3.5.4") diff --git a/libs/tkwsm/test/conanfile.py b/libs/tkwsm/test/conanfile.py index ee1ae1c54f..89a0e3b97a 100644 --- a/libs/tkwsm/test/conanfile.py +++ b/libs/tkwsm/test/conanfile.py @@ -62,4 +62,4 @@ def requirements(self): self.requires("tkwsm/0.3.7") self.requires("tkassert/0.3.4@tket/stable") self.requires("tkrng/0.3.3@tket/stable") - self.requires("catch2/3.5.2") + self.requires("catch2/3.5.4") diff --git a/pytket/binders/circuit/Circuit/add_op.cpp b/pytket/binders/circuit/Circuit/add_op.cpp index 11aaada87c..c26e0d7614 100644 --- a/pytket/binders/circuit/Circuit/add_op.cpp +++ b/pytket/binders/circuit/Circuit/add_op.cpp @@ -1317,6 +1317,42 @@ void init_circuit_add_op(py::class_> &c) { "\n\n:return: the new :py:class:`Circuit`", py::arg("angle0"), py::arg("angle1"), py::arg("angle2"), py::arg("qubit")) + .def( + "GPI", + [](Circuit *circ, const Expr &angle, unsigned qb, + const py::kwargs &kwargs) { + return add_gate_method_oneparam( + circ, OpType::GPI, angle, {qb}, kwargs); + }, + "Appends a GPI gate with a possibly symbolic angle " + "(specified in half-turns)." + "\n\n:return: the new :py:class:`Circuit`", + py::arg("angle"), py::arg("qubit")) + .def( + "GPI2", + [](Circuit *circ, const Expr &angle, unsigned qb, + const py::kwargs &kwargs) { + return add_gate_method_oneparam( + circ, OpType::GPI, angle, {qb}, kwargs); + }, + "Appends a GPI2 gate with a possibly symbolic angle " + "(specified in half-turns)." + "\n\n:return: the new :py:class:`Circuit`", + py::arg("angle"), py::arg("qubit")) + .def( + "AAMS", + [](Circuit *circ, const Expr &angle0, const Expr &angle1, + const Expr &angle2, const unsigned &qb0, const unsigned &qb1, + const py::kwargs &kwargs) { + return add_gate_method_manyparams( + circ, OpType::AAMS, {angle0, angle1, angle2}, {qb0, qb1}, + kwargs); + }, + "Appends an AAMS gate with possibly symbolic angles " + "(specified in half-turns)." + "\n\n:return: the new :py:class:`Circuit`", + py::arg("angle0"), py::arg("angle1"), py::arg("angle2"), + py::arg("qubit0"), py::arg("qubit1")) .def( "TK1", [](Circuit *circ, const Expr &angle0, const Expr &angle1, @@ -1940,6 +1976,42 @@ void init_circuit_add_op(py::class_> &c) { "\n\n:return: the new :py:class:`Circuit`", py::arg("angle0"), py::arg("angle1"), py::arg("angle2"), py::arg("qubit")) + .def( + "GPI", + [](Circuit *circ, const Expr &angle, const Qubit &qb, + const py::kwargs &kwargs) { + return add_gate_method_oneparam( + circ, OpType::GPI, angle, {qb}, kwargs); + }, + "Appends a GPI gate with a possibly symbolic angle " + "(specified in half-turns)." + "\n\n:return: the new :py:class:`Circuit`", + py::arg("angle"), py::arg("qubit")) + .def( + "GPI2", + [](Circuit *circ, const Expr &angle, const Qubit &qb, + const py::kwargs &kwargs) { + return add_gate_method_oneparam( + circ, OpType::GPI2, angle, {qb}, kwargs); + }, + "Appends a GPI2 gate with a possibly symbolic angle " + "(specified in half-turns)." + "\n\n:return: the new :py:class:`Circuit`", + py::arg("angle"), py::arg("qubit")) + .def( + "AAMS", + [](Circuit *circ, const Expr &angle0, const Expr &angle1, + const Expr &angle2, const Qubit &qb0, const Qubit &qb1, + const py::kwargs &kwargs) { + return add_gate_method_manyparams( + circ, OpType::AAMS, {angle0, angle1, angle2}, {qb0, qb1}, + kwargs); + }, + "Appends an AAMS gate with possibly symbolic angles " + "(specified in half-turns)." + "\n\n:return: the new :py:class:`Circuit`", + py::arg("angle0"), py::arg("angle1"), py::arg("angle2"), + py::arg("qubit0"), py::arg("qubit1")) .def( "TK1", [](Circuit *circ, const Expr &angle0, const Expr &angle1, diff --git a/pytket/binders/circuit/boxes.cpp b/pytket/binders/circuit/boxes.cpp index f5b657b68b..809dab205b 100644 --- a/pytket/binders/circuit/boxes.cpp +++ b/pytket/binders/circuit/boxes.cpp @@ -169,7 +169,16 @@ void init_boxes(py::module &m) { "any Circuit that the CircBox has been added to " "(via Circuit.add_circbox). \n\n:param symbol_map: " "A map from SymPy symbols to SymPy expressions", - py::arg("symbol_map")); + py::arg("symbol_map")) + .def_property( + "circuit_name", &CircBox::get_circuit_name, + &CircBox::set_circuit_name, + ":return: the name of the contained circuit. " + "\n\n WARNING: " + "Setting this property mutates the CircBox and " + "any changes are propagated to " + "any Circuit that the CircBox has been added to " + "(via Circuit.add_circbox)."); py::class_, Op>( m, "Unitary1qBox", "A user-defined one-qubit operation specified by a unitary matrix.") @@ -972,7 +981,11 @@ void init_boxes(py::module &m) { "A user-defined multiplexed rotation gate (i.e. " "uniformly controlled single-axis rotations) specified by " "a map from bitstrings to " CLSOBJS(Op) - "or a list of bitstring-" CLSOBJS(Op) " pairs") + "or a list of bitstring-" CLSOBJS(Op) " pairs. " + "Implementation based on arxiv.org/abs/quant-ph/0410066. " + "The decomposed circuit has at most 2^k single-qubit rotations, " + "2^k CX gates, and two additional H gates if the rotation axis is X. " + "k is the number of control qubits.") .def( py::init([](const py_ctrl_op_map_t_alt & bitstring_op_pairs){ return MultiplexedRotationBox(to_cpp_ctrl_op_map_t(bitstring_op_pairs));}), @@ -1039,7 +1052,11 @@ void init_boxes(py::module &m) { "A user-defined multiplexed U2 gate (i.e. uniformly controlled U2 " "gate) specified by a " "map from bitstrings to " CLSOBJS(Op) - "or a list of bitstring-" CLSOBJS(Op) " pairs") + "or a list of bitstring-" CLSOBJS(Op) " pairs" + "Implementation based on arxiv.org/abs/quant-ph/0410066. " + "The decomposed circuit has at most 2^k single-qubit gates, 2^k -1 CX gates, " + "and a k+1 qubit DiagonalBox at the end. " + "k is the number of control qubits.") .def( py::init([](const py_ctrl_op_map_t_alt & bitstring_op_pairs, bool impl_diag){ return MultiplexedU2Box(to_cpp_ctrl_op_map_t(bitstring_op_pairs), impl_diag);}), @@ -1086,7 +1103,14 @@ void init_boxes(py::module &m) { m, "MultiplexedTensoredU2Box", "A user-defined multiplexed tensor product of U2 gates specified by a " "map from bitstrings to lists of " CLSOBJS(Op) - "or a list of bitstring-list(" CLSOBJS(Op) ") pairs") + "or a list of bitstring-list(" CLSOBJS(Op) ") pairs. " + "A box with k control qubits and t target qubits is implemented as t " + "k-controlled multiplexed-U2 gates with their diagonal " + "components merged and commuted to the end. The resulting circuit contains " + "t non-diagonal components of the multiplexed-U2 decomposition, t k-controlled " + "multiplexed-Rz boxes, and a k-qubit DiagonalBox at the end. " + "The total CX count is at most 2^k(2t+1)-t-2." + ) .def( py::init([](const py_ctrl_tensored_op_map_t_alt & bitstring_op_pairs){ return MultiplexedTensoredU2Box(to_cpp_ctrl_op_map_t(bitstring_op_pairs));}), @@ -1123,7 +1147,11 @@ void init_boxes(py::module &m) { py::class_, Op>( m, "StatePreparationBox", "A box for preparing quantum states using multiplexed-Ry and " - "multiplexed-Rz gates") + "multiplexed-Rz gates. " + "Implementation based on Theorem 9 of " + "arxiv.org/abs/quant-ph/0406176. " + "The decomposed circuit has at most 2*(2^n-2) CX gates, and " + "2^n-2 CX gates if the coefficients are all real.") .def( py::init(), "Construct from a statevector\n\n" @@ -1153,7 +1181,10 @@ void init_boxes(py::module &m) { py::class_, Op>( m, "DiagonalBox", "A box for synthesising a diagonal unitary matrix into a sequence of " - "multiplexed-Rz gates.") + "multiplexed-Rz gates. " + "Implementation based on Theorem 7 of " + "arxiv.org/abs/quant-ph/0406176. " + "The decomposed circuit has at most 2^n-2 CX gates.") .def( py::init(), "Construct from the diagonal entries of the unitary operator. The " diff --git a/pytket/binders/circuit/main.cpp b/pytket/binders/circuit/main.cpp index b971ae6850..2687745a63 100644 --- a/pytket/binders/circuit/main.cpp +++ b/pytket/binders/circuit/main.cpp @@ -186,6 +186,28 @@ PYBIND11_MODULE(circuit, m) { "\\end{array} \\right] = e^{\\frac12 i\\pi(\\lambda+\\phi)} " "\\mathrm{Rz}(\\phi) \\mathrm{Ry}(\\theta) " "\\mathrm{Rz}(\\lambda)`") + .value( + "GPI", OpType::GPI, + ":math:`(\\phi) \\mapsto \\left[ \\begin{array}{cc} 0 & " + "e^{-i\\pi\\phi} \\\\ e^{i\\pi\\phi} & 0 \\end{array} \\right]`") + .value( + "GPI2", OpType::GPI2, + ":math:`(\\phi) \\mapsto \\frac{1}{\\sqrt 2} \\left[ " + "\\begin{array}{cc} 1 & -ie^{-i\\pi\\phi} \\\\ -ie^{i\\pi\\phi} & " + "1 \\end{array} \\right]`") + .value( + "AAMS", OpType::AAMS, + ":math:`(\\theta, \\phi_0, \\phi_1) \\mapsto \\left[ " + "\\begin{array}{cccc} \\cos\\frac{\\pi\\theta}{2} & 0 & 0 & " + "-ie^{-i\\pi(\\phi_0+\\phi_1)}\\sin\\frac{\\pi\\theta}{2} \\\\ " + "0 & " + "\\cos\\frac{\\pi\\theta}{2} & " + "-ie^{i\\pi(\\phi_1-\\phi_0)}\\sin\\frac{\\pi\\theta}{2} & 0 \\\\ 0 " + "& " + "-ie^{i\\pi(\\phi_0-\\phi_1)}\\sin\\frac{\\pi\\theta}{2} & " + "\\cos\\frac{\\pi\\theta}{2} & 0 \\\\ " + "-ie^{i\\pi(\\phi_0+\\phi_1)}\\sin\\frac{\\pi\\theta}{2} & 0 & 0 & " + "\\cos\\frac{\\pi\\theta}{2} \\end{array} \\right]`") .value( "TK1", OpType::TK1, ":math:`(\\alpha, \\beta, \\gamma) \\mapsto " diff --git a/pytket/binders/circuit_library.cpp b/pytket/binders/circuit_library.cpp index 050d071f07..0fcf6706c7 100644 --- a/pytket/binders/circuit_library.cpp +++ b/pytket/binders/circuit_library.cpp @@ -103,9 +103,6 @@ PYBIND11_MODULE(circuit_library, library_m) { library_m.def( "SWAP_using_CX_1", &CircPool::SWAP_using_CX_1, "Equivalent to SWAP, using three CX, outer CX have control on qubit 1"); - library_m.def( - "two_Rz1", &CircPool::two_Rz1, - "A two-qubit circuit with an Rz(1) on each qubit"); library_m.def("X1_CX", &CircPool::X1_CX, "X[1]; CX[0,1]"); library_m.def("Z0_CX", &CircPool::Z0_CX, "Z[0]; CX[0,1] "); diff --git a/pytket/binders/passes.cpp b/pytket/binders/passes.cpp index 5088217b7d..22f07ff06f 100644 --- a/pytket/binders/passes.cpp +++ b/pytket/binders/passes.cpp @@ -288,10 +288,15 @@ PYBIND11_MODULE(passes, m) { py::class_, BasePass>( m, "SequencePass", "A sequence of compilation passes.") .def( - py::init &>(), - "Construct from a list of compilation passes arranged in " - "order of application.", - py::arg("pass_list")) + py::init &, bool>(), + "Construct from a list of compilation passes arranged in order of " + "application." + "\n\n:param pass_list: sequence of passes" + "\n:param strict: if True (the default), check that all " + "postconditions and preconditions of the passes in the sequence are " + "compatible and raise an exception if not." + "\n:return: a pass that applies the sequence", + py::arg("pass_list"), py::arg("strict") = true) .def("__str__", [](const BasePass &) { return ""; }) .def( "get_sequence", &SequencePass::get_sequence, @@ -514,17 +519,6 @@ PYBIND11_MODULE(passes, m) { "add any new gate types.\n\n" "When merging rotations with the same op group name, the merged " "operation keeps the same name."); - m.def( - "SynthesiseHQS", - []() { - tket_log()->warn( - "SynthesiseHQS is deprecated. It will be removed " - "after pytket v1.25."); - return SynthesiseHQS(); - }, - "Optimises and converts a circuit consisting of CX and single-qubit " - "gates into one containing only ZZMax, PhasedX, Rz and Phase. " - "DEPRECATED: will be removed after pytket 1.25."); m.def( "SynthesiseTK", &SynthesiseTK, "Optimises and converts all gates to TK2, TK1 and Phase gates."); @@ -532,8 +526,15 @@ PYBIND11_MODULE(passes, m) { "SynthesiseTket", &SynthesiseTket, "Optimises and converts all gates to CX, TK1 and Phase gates."); m.def( - "SynthesiseOQC", &SynthesiseOQC, - "Optimises and converts all gates to ECR, Rz, SX and Phase."); + "SynthesiseOQC", + []() { + tket_log()->warn( + "SynthesiseOQC is deprecated. It will be removed " + "after pytket v1.28."); + return SynthesiseOQC(); + }, + "Optimises and converts all gates to ECR, Rz, SX and Phase. " + "DEPRECATED: will be removed after pytket 1.28."); m.def( "SynthesiseUMD", &SynthesiseUMD, "Optimises and converts all gates to XXPhase, PhasedX, Rz and Phase."); @@ -542,8 +543,8 @@ PYBIND11_MODULE(passes, m) { "Squash sequences of single-qubit gates to TK1 gates."); m.def( "SquashRzPhasedX", &SquashRzPhasedX, - "Squash single qubit gates into PhasedX and Rz gates. " - "Commute Rz gates to the back if possible."); + "Squash single qubit gates into PhasedX and Rz gates. Also remove " + "identity gates. Commute Rz gates to the back if possible."); m.def( "FlattenRegisters", &FlattenRegisters, "Merges all quantum and classical registers into their " @@ -832,6 +833,15 @@ PYBIND11_MODULE(passes, m) { "\n:return: a pass to perform the rewriting", py::arg("transform") = std::nullopt, py::arg("allow_swaps") = true); + m.def( + "CliffordPushThroughMeasures", &gen_clifford_push_through_pass, + "An optimisation pass that resynthesise a Clifford subcircuit " + "before end of circuit Measurement operations by implementing " + "the action of the Clifford as a mutual diagonalisation circuit " + "and a permutation on output measurements realised as a series " + "of classical operations." + "\n: return: a pass to simplify end of circuit Clifford gates."); + m.def( "DecomposeSwapsToCXs", &gen_decompose_routing_gates_to_cxs_pass, "Construct a pass to decompose SWAP and BRIDGE gates to CX gates, " diff --git a/pytket/binders/transform.cpp b/pytket/binders/transform.cpp index 5e68696ba7..91a1c12276 100644 --- a/pytket/binders/transform.cpp +++ b/pytket/binders/transform.cpp @@ -22,6 +22,7 @@ #include "tket/Circuit/Circuit.hpp" #include "tket/Transformations/BasicOptimisation.hpp" +#include "tket/Transformations/CliffordOptimisation.hpp" #include "tket/Transformations/Combinator.hpp" #include "tket/Transformations/ContextualReduction.hpp" #include "tket/Transformations/Decomposition.hpp" @@ -414,6 +415,16 @@ PYBIND11_MODULE(transform, m) { "Decompose CnX gates to 2-qubit gates and single qubit gates. " "For every two CnX gates, reorder their control qubits to improve " "the chance of gate cancellation.") + .def_static( + "PushCliffordsThroughMeasures", + &Transforms::push_cliffords_through_measures, + "Derives a new set of end-of-Circuit measurement operators " + "by acting on end-of-Circuit measurements with a Clifford " + "subcircuit. The new set of measurement operators is necessarily " + "commuting and is implemented by adding a new mutual diagonalisation " + "Clifford subcirciuit to the end of the Circuit and implementing the " + "remaining diagonal measurement operators by measuring and permuting " + "the output.") .def_static( "round_angles", &Transforms::round_angles, "Rounds angles to the nearest :math:`\\pi / 2^n`." diff --git a/pytket/binders/unitid.cpp b/pytket/binders/unitid.cpp index e662f3aa00..3d9d344807 100644 --- a/pytket/binders/unitid.cpp +++ b/pytket/binders/unitid.cpp @@ -204,6 +204,15 @@ PYBIND11_MODULE(unit_id, m) { py::arg("name"), py::arg("index")) .def("__eq__", &py_equals) .def("__hash__", [](const Bit &b) { return hash_value(b); }) + .def(py::pickle( + [](const Bit &b) { return py::make_tuple(b.reg_name(), b.index()); }, + [](const py::tuple &t) { + if (t.size() != 2) + throw std::runtime_error( + "Invalid state: tuple size: " + std::to_string(t.size())); + return Bit( + t[0].cast(), t[1].cast>()); + })) .def( "to_list", [](const Bit &b) { return py::object(json(b)).cast(); }, diff --git a/pytket/conanfile.py b/pytket/conanfile.py index dfdf69cff7..0f2b30f322 100644 --- a/pytket/conanfile.py +++ b/pytket/conanfile.py @@ -32,7 +32,7 @@ def package(self): cmake.install() def requirements(self): - self.requires("tket/1.2.101@tket/stable") + self.requires("tket/1.2.117@tket/stable") self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") self.requires("tkassert/0.3.4@tket/stable") @@ -40,6 +40,6 @@ def requirements(self): self.requires("tktokenswap/0.3.7@tket/stable") self.requires("symengine/0.11.2") self.requires("gmp/6.3.0") - self.requires("pybind11/2.11.1") + self.requires("pybind11/2.12.0") self.requires("nlohmann_json/3.11.3") self.requires("pybind11_json/0.2.13") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index d9504da36f..29ea0efdf9 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -1,6 +1,42 @@ Changelog ========= +1.27.0 (April 2024) +------------------- + +General: + +* Remove deprecated ``SynthesiseHQS`` pass. + +Features: + +* Add ``circuit_name`` property to ``CircBox``. +* Enable pickling of ``Bit`` objects. +* New optimisation ``Transform.PushCliffordsThroughMeasures()`` and pass + ``CliffordPushThroughMeasures`` that optimises Clifford subcircuits + before end of circuit measurement gates. +* Add ``OpType.GPI``, ``OpType.GPI2`` and ``OpType.AAMS``. +* Allow construction of ``SequencePass`` without predicate checks, by means of + new ``strict`` argument to the constructor (defaulting to ``True``). + +Fixes: + +* Correct handling of ``CustomGate`` when converting from pytket to QASM. +* Ensure that ECR, CS and CSdg operations have gate definitions in QASM + conversion. +* Correct position of custom gate definitions needed for conditional operations + in QASM conversion. +* Fix ``DelayMeasures()`` pass for circuits where bits are reused as measurement + targets. +* When adding operations to a circuit, check for invalid wires before adding a + vertex to the circuit. +* Make ``RemoveRedundancies`` pass remove ``OpType.Phase`` gates. +* Remove support for wasm functions with multiple return values. + +Deprecations: + +* Deprecate ``SynthesiseOQC`` pass. + 1.26.0 (March 2024) ------------------- @@ -20,11 +56,12 @@ Features: * Add python binding for ``UnitaryRevTableau``. * Add ``TermSequenceBox``, for circuit synthesis of a series of Pauli Exponentials, where the ordering of terms can be changed. - + Fixes: * Add missing op types to methods for converting Clifford circuits to unitary tableaux. +* Require scipy >= 1.13 and quimb >= 1.8 for ZX module. 1.25.0 (February 2024) ---------------------- diff --git a/pytket/docs/conf.py b/pytket/docs/conf.py index 4a5fcc0833..9d4e85b76d 100644 --- a/pytket/docs/conf.py +++ b/pytket/docs/conf.py @@ -38,9 +38,9 @@ author = "Quantinuum" # The short X.Y version -version = "1.26" +version = "1.27" # The full version, including alpha/beta/rc tags -release = "1.26.0" +release = "1.27.0" # -- General configuration --------------------------------------------------- diff --git a/pytket/docs/requirements.txt b/pytket/docs/requirements.txt index f2462fe93f..a4988a4278 100644 --- a/pytket/docs/requirements.txt +++ b/pytket/docs/requirements.txt @@ -1,6 +1,6 @@ sphinx >= 4.5, <7 sphinx_autodoc_annotation >= 1.0 -sphinx_book_theme ~= 1.0.1 +sphinx_book_theme ~= 1.1.2 sphinx-copybutton jupyter-sphinx ipykernel diff --git a/pytket/package.md b/pytket/package.md index 32339d3c34..5bd7ee3317 100644 --- a/pytket/package.md +++ b/pytket/package.md @@ -1,4 +1,4 @@ -Pytket is a python module for interfacing with TKET, an optimising compiler for quantum circuits developed by [Quantinuum](https://www.quantinuum.com/). In addition to pytket there are several extension modules for accessing a range of quantum hardware and classical simulators. The extension modules also provide integration with several widely used quantum software tools. +Pytket is a python module for interfacing with TKET, a quantum computing toolkit and optimising compiler developed by [Quantinuum](https://www.quantinuum.com/). In addition to pytket there are several pytket extension modules for accessing a range of quantum hardware and classical simulators. The extension modules also allow circuit conversion between several widely used quantum software tools including qiskit, cirq and pennylane. The source code for the TKET compiler can be found in [this github repository](https://github.com/CQCL/tket). @@ -39,6 +39,8 @@ User support: tket-support@cambridgequantum.com For discussion, join the public slack channel [here](https://join.slack.com/t/tketusers/shared_invite/zt-18qmsamj9-UqQFVdkRzxnXCcKtcarLRA). +There is also a [pytket tag](https://quantumcomputing.stackexchange.com/questions/tagged/pytket) on quantum computing stack exchange. + Mailing list: join [here](https://list.cambridgequantum.com/cgi-bin/mailman/listinfo/tket-users). ## Citation @@ -49,4 +51,4 @@ If your work is on the topic of specific compilation tasks, it may be more appro - "On the qubit routing problem" for qubit placement (a.k.a. allocation) and routing (a.k.a. swap network insertion, connectivity solving). https://arxiv.org/abs/1902.08091 . - "Phase Gadget Synthesis for Shallow Circuits" for representing exponentiated Pauli operators in the ZX calculus and their circuit decompositions. https://arxiv.org/abs/1906.01734 . -- "A Generic Compilation Strategy for the Unitary Coupled Cluster Ansatz" for sequencing of terms in Trotterisation and Pauli diagonalisation. https://arxiv.org/abs/2007.10515 . \ No newline at end of file +- "A Generic Compilation Strategy for the Unitary Coupled Cluster Ansatz" for sequencing of terms in Trotterisation and Pauli diagonalisation. https://arxiv.org/abs/2007.10515 . diff --git a/pytket/pytket/__init__.py b/pytket/pytket/__init__.py index e62f2b5ebf..6da5f11dc2 100755 --- a/pytket/pytket/__init__.py +++ b/pytket/pytket/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Python Interface to CQC tket +"""Python Interface to tket """ from pytket.circuit import ( Circuit, diff --git a/pytket/pytket/_tket/circuit.pyi b/pytket/pytket/_tket/circuit.pyi index 76561123b6..163ac46444 100644 --- a/pytket/pytket/_tket/circuit.pyi +++ b/pytket/pytket/_tket/circuit.pyi @@ -30,7 +30,7 @@ class BarrierOp(Op): class BasisOrder: """ Enum for readout basis and ordering. - Readouts are viewed in increasing lexicographic order (ILO) of the bit's UnitID. This is our default convention for column indexing for ALL readout forms (shots, counts, statevector, and unitaries). e.g. :math:`\lvert abc \rangle` corresponds to the readout: ('c', 0) --> :math:`a`, ('c', 1) --> :math:`b`, ('d', 0) --> :math:`c` + Readouts are viewed in increasing lexicographic order (ILO) of the bit's UnitID. This is our default convention for column indexing for ALL readout forms (shots, counts, statevector, and unitaries). e.g. :math:`\\lvert abc \\rangle` corresponds to the readout: ('c', 0) --> :math:`a`, ('c', 1) --> :math:`b`, ('d', 0) --> :math:`c` For statevector and unitaries, the string abc is interpreted as an index in a big-endian (BE) fashion. e.g. the statevector :math:`(a_{00}, a_{01}, a_{10}, a_{11})` Some backends (Qiskit, ProjectQ, etc.) use a DLO-BE (decreasing lexicographic order, big-endian) convention. This is the same as ILO-LE (little-endian) for statevectors and unitaries, but gives shot tables/readouts in a counter-intuitive manner. Every backend and matrix-based box has a BasisOrder option which can toggle between ILO-BE (ilo) and DLO-BE (dlo). @@ -135,6 +135,16 @@ class CircBox(Op): :param symbol_map: A map from SymPy symbols to SymPy expressions """ + @property + def circuit_name(self) -> str | None: + """ + :return: the name of the contained circuit. + + WARNING: Setting this property mutates the CircBox and any changes are propagated to any Circuit that the CircBox has been added to (via Circuit.add_circbox). + """ + @circuit_name.setter + def circuit_name(self, arg1: str) -> None: + ... class Circuit: """ Encapsulates a quantum circuit using a DAG representation. @@ -151,6 +161,20 @@ class Circuit: Construct Circuit instance from JSON serializable dictionary representation of the Circuit. """ @typing.overload + def AAMS(self, angle0: sympy.Expr | float, angle1: sympy.Expr | float, angle2: sympy.Expr | float, qubit0: int, qubit1: int, **kwargs: Any) -> Circuit: + """ + Appends an AAMS gate with possibly symbolic angles (specified in half-turns). + + :return: the new :py:class:`Circuit` + """ + @typing.overload + def AAMS(self, angle0: sympy.Expr | float, angle1: sympy.Expr | float, angle2: sympy.Expr | float, qubit0: pytket._tket.unit_id.Qubit, qubit1: pytket._tket.unit_id.Qubit, **kwargs: Any) -> Circuit: + """ + Appends an AAMS gate with possibly symbolic angles (specified in half-turns). + + :return: the new :py:class:`Circuit` + """ + @typing.overload def CCX(self, control_0: int, control_1: int, target: int, **kwargs: Any) -> Circuit: """ Appends a CCX gate on the wires for the specified control and target qubits. @@ -431,6 +455,34 @@ class Circuit: :return: the new :py:class:`Circuit` """ @typing.overload + def GPI(self, angle: sympy.Expr | float, qubit: int, **kwargs: Any) -> Circuit: + """ + Appends a GPI gate with a possibly symbolic angle (specified in half-turns). + + :return: the new :py:class:`Circuit` + """ + @typing.overload + def GPI(self, angle: sympy.Expr | float, qubit: pytket._tket.unit_id.Qubit, **kwargs: Any) -> Circuit: + """ + Appends a GPI gate with a possibly symbolic angle (specified in half-turns). + + :return: the new :py:class:`Circuit` + """ + @typing.overload + def GPI2(self, angle: sympy.Expr | float, qubit: int, **kwargs: Any) -> Circuit: + """ + Appends a GPI2 gate with a possibly symbolic angle (specified in half-turns). + + :return: the new :py:class:`Circuit` + """ + @typing.overload + def GPI2(self, angle: sympy.Expr | float, qubit: pytket._tket.unit_id.Qubit, **kwargs: Any) -> Circuit: + """ + Appends a GPI2 gate with a possibly symbolic angle (specified in half-turns). + + :return: the new :py:class:`Circuit` + """ + @typing.overload def H(self, qubit: int, **kwargs: Any) -> Circuit: """ Appends a Hadamard gate. @@ -932,7 +984,7 @@ class Circuit: :param n_bits: The number of classical bits in the circuit :param name: Optional name for the circuit. """ - def __iter__(self) -> typing.Iterator: + def __iter__(self) -> typing.Iterator[Command]: """ Iterate through the circuit, a Command at a time. """ @@ -2695,7 +2747,7 @@ class CustomGateDef: """ class DiagonalBox(Op): """ - A box for synthesising a diagonal unitary matrix into a sequence of multiplexed-Rz gates. + A box for synthesising a diagonal unitary matrix into a sequence of multiplexed-Rz gates. Implementation based on Theorem 7 of arxiv.org/abs/quant-ph/0406176. The decomposed circuit has at most 2^n-2 CX gates. """ def __init__(self, diagonal: NDArray[numpy.complex128], upper_triangle: bool = True) -> None: """ @@ -2829,7 +2881,7 @@ class MultiBitOp(ClassicalEvalOp): """ class MultiplexedRotationBox(Op): """ - A user-defined multiplexed rotation gate (i.e. uniformly controlled single-axis rotations) specified by a map from bitstrings to :py:class:`Op` sor a list of bitstring-:py:class:`Op` s pairs + A user-defined multiplexed rotation gate (i.e. uniformly controlled single-axis rotations) specified by a map from bitstrings to :py:class:`Op` sor a list of bitstring-:py:class:`Op` s pairs. Implementation based on arxiv.org/abs/quant-ph/0410066. The decomposed circuit has at most 2^k single-qubit rotations, 2^k CX gates, and two additional H gates if the rotation axis is X. k is the number of control qubits. """ @typing.overload def __init__(self, bistring_to_op_list: typing.Sequence[tuple[typing.Sequence[bool], Op]]) -> None: @@ -2869,7 +2921,7 @@ class MultiplexedRotationBox(Op): """ class MultiplexedTensoredU2Box(Op): """ - A user-defined multiplexed tensor product of U2 gates specified by a map from bitstrings to lists of :py:class:`Op` sor a list of bitstring-list(:py:class:`Op` s) pairs + A user-defined multiplexed tensor product of U2 gates specified by a map from bitstrings to lists of :py:class:`Op` sor a list of bitstring-list(:py:class:`Op` s) pairs. A box with k control qubits and t target qubits is implemented as t k-controlled multiplexed-U2 gates with their diagonal components merged and commuted to the end. The resulting circuit contains t non-diagonal components of the multiplexed-U2 decomposition, t k-controlled multiplexed-Rz boxes, and a k-qubit DiagonalBox at the end. The total CX count is at most 2^k(2t+1)-t-2. """ @typing.overload def __init__(self, bistring_to_op_list: typing.Sequence[tuple[typing.Sequence[bool], typing.Sequence[Op]]]) -> None: @@ -2901,7 +2953,7 @@ class MultiplexedTensoredU2Box(Op): """ class MultiplexedU2Box(Op): """ - A user-defined multiplexed U2 gate (i.e. uniformly controlled U2 gate) specified by a map from bitstrings to :py:class:`Op` sor a list of bitstring-:py:class:`Op` s pairs + A user-defined multiplexed U2 gate (i.e. uniformly controlled U2 gate) specified by a map from bitstrings to :py:class:`Op` sor a list of bitstring-:py:class:`Op` s pairsImplementation based on arxiv.org/abs/quant-ph/0410066. The decomposed circuit has at most 2^k single-qubit gates, 2^k -1 CX gates, and a k+1 qubit DiagonalBox at the end. k is the number of control qubits. """ @typing.overload def __init__(self, bistring_to_op_list: typing.Sequence[tuple[typing.Sequence[bool], Op]], impl_diag: bool = True) -> None: @@ -3028,7 +3080,7 @@ class Op: @property def params(self) -> list[sympy.Expr | float]: """ - Angular parameters of the op, in half-turns (e.g. 1.0 half-turns is :math:`\pi` radians). The parameters returned are constrained to the appropriate canonical range, which is usually the half-open interval [0,2) but for some operations (e.g. Rx, Ry and Rz) is [0,4). + Angular parameters of the op, in half-turns (e.g. 1.0 half-turns is :math:`\\pi` radians). The parameters returned are constrained to the appropriate canonical range, which is usually the half-open interval [0,2) but for some operations (e.g. Rx, Ry and Rz) is [0,4). """ @property def transpose(self) -> Op: @@ -3046,81 +3098,87 @@ class OpType: Members: - Phase : Global phase: :math:`(\alpha) \mapsto \left[ \begin{array}{c} e^{i\pi\alpha} \end{array} \right]` + Phase : Global phase: :math:`(\\alpha) \\mapsto \\left[ \\begin{array}{c} e^{i\\pi\\alpha} \\end{array} \\right]` + + Z : Pauli Z: :math:`\\left[ \\begin{array}{cc} 1 & 0 \\\\ 0 & -1 \\end{array} \\right]` + + X : Pauli X: :math:`\\left[ \\begin{array}{cc} 0 & 1 \\\\ 1 & 0 \\end{array} \\right]` + + Y : Pauli Y: :math:`\\left[ \\begin{array}{cc} 0 & -i \\\\ i & 0 \\end{array} \\right]` - Z : Pauli Z: :math:`\left[ \begin{array}{cc} 1 & 0 \\ 0 & -1 \end{array} \right]` + S : :math:`\\left[ \\begin{array}{cc} 1 & 0 \\\\ 0 & i \\end{array} \\right] = \\mathrm{U1}(\\frac12)` - X : Pauli X: :math:`\left[ \begin{array}{cc} 0 & 1 \\ 1 & 0 \end{array} \right]` + Sdg : :math:`\\mathrm{S}^{\\dagger} = \\left[ \\begin{array}{cc} 1 & 0 \\\\ 0 & -i \\end{array} \\right] = \\mathrm{U1}(-\\frac12)` - Y : Pauli Y: :math:`\left[ \begin{array}{cc} 0 & -i \\ i & 0 \end{array} \right]` + T : :math:`\\left[ \\begin{array}{cc} 1 & 0 \\\\ 0 & e^{i\\pi/4} \\end{array} \\right] = \\mathrm{U1}(\\frac14)` - S : :math:`\left[ \begin{array}{cc} 1 & 0 \\ 0 & i \end{array} \right] = \mathrm{U1}(\frac12)` + Tdg : :math:`\\mathrm{T}^{\\dagger} = \\left[ \\begin{array}{cc} 1 & 0 \\\\ 0 & e^{-i\\pi/4} \\end{array} \\right] = \\mathrm{U1}(-\\frac14)` - Sdg : :math:`\mathrm{S}^{\dagger} = \left[ \begin{array}{cc} 1 & 0 \\ 0 & -i \end{array} \right] = \mathrm{U1}(-\frac12)` + V : :math:`\\frac{1}{\\sqrt 2} \\left[ \\begin{array}{cc} 1 & -i \\\\ -i & 1 \\end{array} \\right] = \\mathrm{Rx}(\\frac12)` - T : :math:`\left[ \begin{array}{cc} 1 & 0 \\ 0 & e^{i\pi/4} \end{array} \right] = \mathrm{U1}(\frac14)` + Vdg : :math:`\\mathrm{V}^{\\dagger} = \\frac{1}{\\sqrt 2} \\left[ \\begin{array}{cc} 1 & i \\\\ i & 1 \\end{array} \\right] = \\mathrm{Rx}(-\\frac12)` - Tdg : :math:`\mathrm{T}^{\dagger} = \left[ \begin{array}{cc} 1 & 0 \\ 0 & e^{-i\pi/4} \end{array} \right] = \mathrm{U1}(-\frac14)` + SX : :math:`\\frac{1}{2} \\left[ \\begin{array}{cc} 1 + i & 1 - i \\\\ 1 - i & 1 + i \\end{array} \\right] = e^{\\frac{i\\pi}{4}}\\mathrm{Rx}(\\frac12)` - V : :math:`\frac{1}{\sqrt 2} \left[ \begin{array}{cc} 1 & -i \\ -i & 1 \end{array} \right] = \mathrm{Rx}(\frac12)` + SXdg : :math:`\\mathrm{SX}^{\\dagger} = \\frac{1}{2} \\left[ \\begin{array}{cc} 1 - i & 1 + i \\\\ 1 + i & 1 - i \\end{array} \\right] = e^{\\frac{-i\\pi}{4}}\\mathrm{Rx}(-\\frac12)` - Vdg : :math:`\mathrm{V}^{\dagger} = \frac{1}{\sqrt 2} \left[ \begin{array}{cc} 1 & i \\ i & 1 \end{array} \right] = \mathrm{Rx}(-\frac12)` + H : Hadamard gate: :math:`\\frac{1}{\\sqrt 2} \\left[ \\begin{array}{cc} 1 & 1 \\\\ 1 & -1 \\end{array} \\right]` - SX : :math:`\frac{1}{2} \left[ \begin{array}{cc} 1 + i & 1 - i \\ 1 - i & 1 + i \end{array} \right] = e^{\frac{i\pi}{4}}\mathrm{Rx}(\frac12)` + Rx : :math:`(\\alpha) \\mapsto e^{-\\frac12 i \\pi \\alpha \\mathrm{X}} = \\left[ \\begin{array}{cc} \\cos\\frac{\\pi\\alpha}{2} & -i\\sin\\frac{\\pi\\alpha}{2} \\\\ -i\\sin\\frac{\\pi\\alpha}{2} & \\cos\\frac{\\pi\\alpha}{2} \\end{array} \\right]` - SXdg : :math:`\mathrm{SX}^{\dagger} = \frac{1}{2} \left[ \begin{array}{cc} 1 - i & 1 + i \\ 1 + i & 1 - i \end{array} \right] = e^{\frac{-i\pi}{4}}\mathrm{Rx}(-\frac12)` + Ry : :math:`(\\alpha) \\mapsto e^{-\\frac12 i \\pi \\alpha \\mathrm{Y}} = \\left[ \\begin{array}{cc} \\cos\\frac{\\pi\\alpha}{2} & -\\sin\\frac{\\pi\\alpha}{2} \\\\ \\sin\\frac{\\pi\\alpha}{2} & \\cos\\frac{\\pi\\alpha}{2} \\end{array} \\right]` - H : Hadamard gate: :math:`\frac{1}{\sqrt 2} \left[ \begin{array}{cc} 1 & 1 \\ 1 & -1 \end{array} \right]` + Rz : :math:`(\\alpha) \\mapsto e^{-\\frac12 i \\pi \\alpha \\mathrm{Z}} = \\left[ \\begin{array}{cc} e^{-\\frac12 i \\pi\\alpha} & 0 \\\\ 0 & e^{\\frac12 i \\pi\\alpha} \\end{array} \\right]` - Rx : :math:`(\alpha) \mapsto e^{-\frac12 i \pi \alpha \mathrm{X}} = \left[ \begin{array}{cc} \cos\frac{\pi\alpha}{2} & -i\sin\frac{\pi\alpha}{2} \\ -i\sin\frac{\pi\alpha}{2} & \cos\frac{\pi\alpha}{2} \end{array} \right]` + U1 : :math:`(\\lambda) \\mapsto \\mathrm{U3}(0, 0, \\lambda) = e^{\\frac12 i\\pi\\lambda} \\mathrm{Rz}(\\lambda)`. U-gates are used by IBM. See https://qiskit.org/documentation/tutorials/circuits/3_summary_of_quantum_operations.html for more information on U-gates. - Ry : :math:`(\alpha) \mapsto e^{-\frac12 i \pi \alpha \mathrm{Y}} = \left[ \begin{array}{cc} \cos\frac{\pi\alpha}{2} & -\sin\frac{\pi\alpha}{2} \\ \sin\frac{\pi\alpha}{2} & \cos\frac{\pi\alpha}{2} \end{array} \right]` + U2 : :math:`(\\phi, \\lambda) \\mapsto \\mathrm{U3}(\\frac12, \\phi, \\lambda) = e^{\\frac12 i\\pi(\\lambda+\\phi)} \\mathrm{Rz}(\\phi) \\mathrm{Ry}(\\frac12) \\mathrm{Rz}(\\lambda)`, defined by matrix multiplication - Rz : :math:`(\alpha) \mapsto e^{-\frac12 i \pi \alpha \mathrm{Z}} = \left[ \begin{array}{cc} e^{-\frac12 i \pi\alpha} & 0 \\ 0 & e^{\frac12 i \pi\alpha} \end{array} \right]` + U3 : :math:`(\\theta, \\phi, \\lambda) \\mapsto \\left[ \\begin{array}{cc} \\cos\\frac{\\pi\\theta}{2} & -e^{i\\pi\\lambda} \\sin\\frac{\\pi\\theta}{2} \\\\ e^{i\\pi\\phi} \\sin\\frac{\\pi\\theta}{2} & e^{i\\pi(\\lambda+\\phi)} \\cos\\frac{\\pi\\theta}{2} \\end{array} \\right] = e^{\\frac12 i\\pi(\\lambda+\\phi)} \\mathrm{Rz}(\\phi) \\mathrm{Ry}(\\theta) \\mathrm{Rz}(\\lambda)` - U1 : :math:`(\lambda) \mapsto \mathrm{U3}(0, 0, \lambda) = e^{\frac12 i\pi\lambda} \mathrm{Rz}(\lambda)`. U-gates are used by IBM. See https://qiskit.org/documentation/tutorials/circuits/3_summary_of_quantum_operations.html for more information on U-gates. + GPI : :math:`(\\phi) \\mapsto \\left[ \\begin{array}{cc} 0 & e^{-i\\pi\\phi} \\\\ e^{i\\pi\\phi} & 0 \\end{array} \\right]` - U2 : :math:`(\phi, \lambda) \mapsto \mathrm{U3}(\frac12, \phi, \lambda) = e^{\frac12 i\pi(\lambda+\phi)} \mathrm{Rz}(\phi) \mathrm{Ry}(\frac12) \mathrm{Rz}(\lambda)`, defined by matrix multiplication + GPI2 : :math:`(\\phi) \\mapsto \\frac{1}{\\sqrt 2} \\left[ \\begin{array}{cc} 1 & -ie^{-i\\pi\\phi} \\\\ -ie^{i\\pi\\phi} & 1 \\end{array} \\right]` - U3 : :math:`(\theta, \phi, \lambda) \mapsto \left[ \begin{array}{cc} \cos\frac{\pi\theta}{2} & -e^{i\pi\lambda} \sin\frac{\pi\theta}{2} \\ e^{i\pi\phi} \sin\frac{\pi\theta}{2} & e^{i\pi(\lambda+\phi)} \cos\frac{\pi\theta}{2} \end{array} \right] = e^{\frac12 i\pi(\lambda+\phi)} \mathrm{Rz}(\phi) \mathrm{Ry}(\theta) \mathrm{Rz}(\lambda)` + AAMS : :math:`(\\theta, \\phi_0, \\phi_1) \\mapsto \\left[ \\begin{array}{cccc} \\cos\\frac{\\pi\\theta}{2} & 0 & 0 & -ie^{-i\\pi(\\phi_0+\\phi_1)}\\sin\\frac{\\pi\\theta}{2} \\\\ 0 & \\cos\\frac{\\pi\\theta}{2} & -ie^{i\\pi(\\phi_1-\\phi_0)}\\sin\\frac{\\pi\\theta}{2} & 0 \\\\ 0 & -ie^{i\\pi(\\phi_0-\\phi_1)}\\sin\\frac{\\pi\\theta}{2} & \\cos\\frac{\\pi\\theta}{2} & 0 \\\\ -ie^{i\\pi(\\phi_0+\\phi_1)}\\sin\\frac{\\pi\\theta}{2} & 0 & 0 & \\cos\\frac{\\pi\\theta}{2} \\end{array} \\right]` - TK1 : :math:`(\alpha, \beta, \gamma) \mapsto \mathrm{Rz}(\alpha) \mathrm{Rx}(\beta) \mathrm{Rz}(\gamma)` + TK1 : :math:`(\\alpha, \\beta, \\gamma) \\mapsto \\mathrm{Rz}(\\alpha) \\mathrm{Rx}(\\beta) \\mathrm{Rz}(\\gamma)` - TK2 : :math:`(\alpha, \beta, \gamma) \mapsto \mathrm{XXPhase}(\alpha) \mathrm{YYPhase}(\beta) \mathrm{ZZPhase}(\gamma)` + TK2 : :math:`(\\alpha, \\beta, \\gamma) \\mapsto \\mathrm{XXPhase}(\\alpha) \\mathrm{YYPhase}(\\beta) \\mathrm{ZZPhase}(\\gamma)` - CX : Controlled :math:`\mathrm{X}` gate + CX : Controlled :math:`\\mathrm{X}` gate - CY : Controlled :math:`\mathrm{Y}` gate + CY : Controlled :math:`\\mathrm{Y}` gate - CZ : Controlled :math:`\mathrm{Z}` gate + CZ : Controlled :math:`\\mathrm{Z}` gate - CH : Controlled :math:`\mathrm{H}` gate + CH : Controlled :math:`\\mathrm{H}` gate - CV : Controlled :math:`\mathrm{V}` gate + CV : Controlled :math:`\\mathrm{V}` gate - CVdg : Controlled :math:`\mathrm{V}^{\dagger}` gate + CVdg : Controlled :math:`\\mathrm{V}^{\\dagger}` gate - CSX : Controlled :math:`\mathrm{SX}` gate + CSX : Controlled :math:`\\mathrm{SX}` gate - CSXdg : Controlled :math:`\mathrm{SX}^{\dagger}` gate + CSXdg : Controlled :math:`\\mathrm{SX}^{\\dagger}` gate - CS : Controlled :math:`\mathrm{S}` gate + CS : Controlled :math:`\\mathrm{S}` gate - CSdg : Controlled :math:`\mathrm{S}^{\dagger}` gate + CSdg : Controlled :math:`\\mathrm{S}^{\\dagger}` gate - CRz : :math:`(\alpha) \mapsto` Controlled :math:`\mathrm{Rz}(\alpha)` gate + CRz : :math:`(\\alpha) \\mapsto` Controlled :math:`\\mathrm{Rz}(\\alpha)` gate - CRx : :math:`(\alpha) \mapsto` Controlled :math:`\mathrm{Rx}(\alpha)` gate + CRx : :math:`(\\alpha) \\mapsto` Controlled :math:`\\mathrm{Rx}(\\alpha)` gate - CRy : :math:`(\alpha) \mapsto` Controlled :math:`\mathrm{Ry}(\alpha)` gate + CRy : :math:`(\\alpha) \\mapsto` Controlled :math:`\\mathrm{Ry}(\\alpha)` gate - CU1 : :math:`(\lambda) \mapsto` Controlled :math:`\mathrm{U1}(\lambda)` gate. Note that this is not equivalent to a :math:`\mathrm{CRz}(\lambda)` up to global phase, differing by an extra :math:`\mathrm{Rz}(\frac{\lambda}{2})` on the control qubit. + CU1 : :math:`(\\lambda) \\mapsto` Controlled :math:`\\mathrm{U1}(\\lambda)` gate. Note that this is not equivalent to a :math:`\\mathrm{CRz}(\\lambda)` up to global phase, differing by an extra :math:`\\mathrm{Rz}(\\frac{\\lambda}{2})` on the control qubit. - CU3 : :math:`(\theta, \phi, \lambda) \mapsto` Controlled :math:`\mathrm{U3}(\theta, \phi, \lambda)` gate. Similar rules apply. + CU3 : :math:`(\\theta, \\phi, \\lambda) \\mapsto` Controlled :math:`\\mathrm{U3}(\\theta, \\phi, \\lambda)` gate. Similar rules apply. CCX : Toffoli gate - ECR : :math:`\frac{1}{\sqrt 2} \left[ \begin{array}{cccc} 0 & 0 & 1 & i \\0 & 0 & i & 1 \\1 & -i & 0 & 0 \\-i & 1 & 0 & 0 \end{array} \right]` + ECR : :math:`\\frac{1}{\\sqrt 2} \\left[ \\begin{array}{cccc} 0 & 0 & 1 & i \\\\0 & 0 & i & 1 \\\\1 & -i & 0 & 0 \\\\-i & 1 & 0 & 0 \\end{array} \\right]` SWAP : Swap gate @@ -3142,7 +3200,7 @@ class OpType: Measure : Z-basis projective measurement, storing the measurement outcome in a specified bit - Reset : Resets the qubit to :math:`\left|0\right>` + Reset : Resets the qubit to :math:`\\left|0\\right>` CircBox : Represents an arbitrary subcircuit @@ -3156,11 +3214,11 @@ class OpType: ExpBox : A two-qubit operation corresponding to a unitary matrix defined as the exponential :math:`e^{itA}` of an arbitrary 4x4 hermitian matrix :math:`A`. - PauliExpBox : An operation defined as the exponential :math:`e^{-\frac{i\pi\alpha}{2} P}` of a tensor :math:`P` of Pauli operations. + PauliExpBox : An operation defined as the exponential :math:`e^{-\\frac{i\\pi\\alpha}{2} P}` of a tensor :math:`P` of Pauli operations. - PauliExpPairBox : A pair of (not necessarily commuting) Pauli exponentials :math:`e^{-\frac{i\pi\alpha}{2} P}` performed in sequence. + PauliExpPairBox : A pair of (not necessarily commuting) Pauli exponentials :math:`e^{-\\frac{i\\pi\\alpha}{2} P}` performed in sequence. - PauliExpCommutingSetBox : An operation defined as a setof commuting exponentials of the form :math:`e^{-\frac{i\pi\alpha}{2} P}` of a tensor :math:`P` of Pauli operations. + PauliExpCommutingSetBox : An operation defined as a setof commuting exponentials of the form :math:`e^{-\\frac{i\\pi\\alpha}{2} P}` of a tensor :math:`P` of Pauli operations. TermSequenceBox : An unordered collection of Pauli exponentials that can be synthesised in any order, causing a change in the unitary operation. Synthesis order depends on the synthesis strategy chosen only. @@ -3172,27 +3230,27 @@ class OpType: DummyBox : A placeholder operation that holds resource data - CustomGate : :math:`(\alpha, \beta, \ldots) \mapsto` A user-defined operation, based on a :py:class:`Circuit` :math:`C` with parameters :math:`\alpha, \beta, \ldots` substituted in place of bound symbolic variables in :math:`C`, as defined by the :py:class:`CustomGateDef`. + CustomGate : :math:`(\\alpha, \\beta, \\ldots) \\mapsto` A user-defined operation, based on a :py:class:`Circuit` :math:`C` with parameters :math:`\\alpha, \\beta, \\ldots` substituted in place of bound symbolic variables in :math:`C`, as defined by the :py:class:`CustomGateDef`. Conditional : An operation to be applied conditionally on the value of some classical register - ISWAP : :math:`(\alpha) \mapsto e^{\frac14 i \pi\alpha (\mathrm{X} \otimes \mathrm{X} + \mathrm{Y} \otimes \mathrm{Y})} = \left[ \begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & \cos\frac{\pi\alpha}{2} & i\sin\frac{\pi\alpha}{2} & 0 \\ 0 & i\sin\frac{\pi\alpha}{2} & \cos\frac{\pi\alpha}{2} & 0 \\ 0 & 0 & 0 & 1 \end{array} \right]` + ISWAP : :math:`(\\alpha) \\mapsto e^{\\frac14 i \\pi\\alpha (\\mathrm{X} \\otimes \\mathrm{X} + \\mathrm{Y} \\otimes \\mathrm{Y})} = \\left[ \\begin{array}{cccc} 1 & 0 & 0 & 0 \\\\ 0 & \\cos\\frac{\\pi\\alpha}{2} & i\\sin\\frac{\\pi\\alpha}{2} & 0 \\\\ 0 & i\\sin\\frac{\\pi\\alpha}{2} & \\cos\\frac{\\pi\\alpha}{2} & 0 \\\\ 0 & 0 & 0 & 1 \\end{array} \\right]` - PhasedISWAP : :math:`(p, t) \mapsto \left[ \begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & \cos\frac{\pi t}{2} & i\sin\frac{\pi t}{2}e^{2i\pi p} & 0 \\ 0 & i\sin\frac{\pi t}{2}e^{-2i\pi p} & \cos\frac{\pi t}{2} & 0 \\ 0 & 0 & 0 & 1 \end{array} \right]` (equivalent to: Rz(p)[0]; Rz(-p)[1]; ISWAP(t); Rz(-p)[0]; Rz(p)[1]) + PhasedISWAP : :math:`(p, t) \\mapsto \\left[ \\begin{array}{cccc} 1 & 0 & 0 & 0 \\\\ 0 & \\cos\\frac{\\pi t}{2} & i\\sin\\frac{\\pi t}{2}e^{2i\\pi p} & 0 \\\\ 0 & i\\sin\\frac{\\pi t}{2}e^{-2i\\pi p} & \\cos\\frac{\\pi t}{2} & 0 \\\\ 0 & 0 & 0 & 1 \\end{array} \\right]` (equivalent to: Rz(p)[0]; Rz(-p)[1]; ISWAP(t); Rz(-p)[0]; Rz(p)[1]) - XXPhase : :math:`(\alpha) \mapsto e^{-\frac12 i \pi\alpha (\mathrm{X} \otimes \mathrm{X})} = \left[ \begin{array}{cccc} \cos\frac{\pi\alpha}{2} & 0 & 0 & -i\sin\frac{\pi\alpha}{2} \\ 0 & \cos\frac{\pi\alpha}{2} & -i\sin\frac{\pi\alpha}{2} & 0 \\ 0 & -i\sin\frac{\pi\alpha}{2} & \cos\frac{\pi\alpha}{2} & 0 \\ -i\sin\frac{\pi\alpha}{2} & 0 & 0 & \cos\frac{\pi\alpha}{2} \end{array} \right]` + XXPhase : :math:`(\\alpha) \\mapsto e^{-\\frac12 i \\pi\\alpha (\\mathrm{X} \\otimes \\mathrm{X})} = \\left[ \\begin{array}{cccc} \\cos\\frac{\\pi\\alpha}{2} & 0 & 0 & -i\\sin\\frac{\\pi\\alpha}{2} \\\\ 0 & \\cos\\frac{\\pi\\alpha}{2} & -i\\sin\\frac{\\pi\\alpha}{2} & 0 \\\\ 0 & -i\\sin\\frac{\\pi\\alpha}{2} & \\cos\\frac{\\pi\\alpha}{2} & 0 \\\\ -i\\sin\\frac{\\pi\\alpha}{2} & 0 & 0 & \\cos\\frac{\\pi\\alpha}{2} \\end{array} \\right]` - YYPhase : :math:`(\alpha) \mapsto e^{-\frac12 i \pi\alpha (\mathrm{Y} \otimes \mathrm{Y})} = \left[ \begin{array}{cccc} \cos\frac{\pi\alpha}{2} & 0 & 0 & i\sin\frac{\pi\alpha}{2} \\ 0 & \cos\frac{\pi\alpha}{2} & -i\sin\frac{\pi\alpha}{2} & 0 \\ 0 & -i\sin\frac{\pi\alpha}{2} & \cos\frac{\pi\alpha}{2} & 0 \\ i\sin\frac{\pi\alpha}{2} & 0 & 0 & \cos\frac{\pi\alpha}{2} \end{array} \right]` + YYPhase : :math:`(\\alpha) \\mapsto e^{-\\frac12 i \\pi\\alpha (\\mathrm{Y} \\otimes \\mathrm{Y})} = \\left[ \\begin{array}{cccc} \\cos\\frac{\\pi\\alpha}{2} & 0 & 0 & i\\sin\\frac{\\pi\\alpha}{2} \\\\ 0 & \\cos\\frac{\\pi\\alpha}{2} & -i\\sin\\frac{\\pi\\alpha}{2} & 0 \\\\ 0 & -i\\sin\\frac{\\pi\\alpha}{2} & \\cos\\frac{\\pi\\alpha}{2} & 0 \\\\ i\\sin\\frac{\\pi\\alpha}{2} & 0 & 0 & \\cos\\frac{\\pi\\alpha}{2} \\end{array} \\right]` - ZZPhase : :math:`(\alpha) \mapsto e^{-\frac12 i \pi\alpha (\mathrm{Z} \otimes \mathrm{Z})} = \left[ \begin{array}{cccc} e^{-\frac12 i \pi\alpha} & 0 & 0 & 0 \\ 0 & e^{\frac12 i \pi\alpha} & 0 & 0 \\ 0 & 0 & e^{\frac12 i \pi\alpha} & 0 \\ 0 & 0 & 0 & e^{-\frac12 i \pi\alpha} \end{array} \right]` + ZZPhase : :math:`(\\alpha) \\mapsto e^{-\\frac12 i \\pi\\alpha (\\mathrm{Z} \\otimes \\mathrm{Z})} = \\left[ \\begin{array}{cccc} e^{-\\frac12 i \\pi\\alpha} & 0 & 0 & 0 \\\\ 0 & e^{\\frac12 i \\pi\\alpha} & 0 & 0 \\\\ 0 & 0 & e^{\\frac12 i \\pi\\alpha} & 0 \\\\ 0 & 0 & 0 & e^{-\\frac12 i \\pi\\alpha} \\end{array} \\right]` XXPhase3 : A 3-qubit gate XXPhase3(α) consists of pairwise 2-qubit XXPhase(α) interactions. Equivalent to XXPhase(α) XXPhase(α) XXPhase(α). - PhasedX : :math:`(\alpha,\beta) \mapsto \mathrm{Rz}(\beta)\mathrm{Rx}(\alpha)\mathrm{Rz}(-\beta)` (matrix-multiplication order) + PhasedX : :math:`(\\alpha,\\beta) \\mapsto \\mathrm{Rz}(\\beta)\\mathrm{Rx}(\\alpha)\\mathrm{Rz}(-\\beta)` (matrix-multiplication order) - NPhasedX : :math:`(\alpha, \beta) \mapsto \mathrm{PhasedX}(\alpha, \beta)^{\otimes n}` (n-qubit gate composed of identical PhasedX in parallel. + NPhasedX : :math:`(\\alpha, \\beta) \\mapsto \\mathrm{PhasedX}(\\alpha, \\beta)^{\\otimes n}` (n-qubit gate composed of identical PhasedX in parallel. - CnRy : :math:`(\alpha)` := n-controlled :math:`\mathrm{Ry}(\alpha)` gate. + CnRy : :math:`(\\alpha)` := n-controlled :math:`\\mathrm{Ry}(\\alpha)` gate. CnX : n-controlled X gate. @@ -3200,15 +3258,15 @@ class OpType: CnZ : n-controlled Z gate. - ZZMax : :math:`e^{-\frac{i\pi}{4}(\mathrm{Z} \otimes \mathrm{Z})}`, a maximally entangling ZZPhase + ZZMax : :math:`e^{-\\frac{i\\pi}{4}(\\mathrm{Z} \\otimes \\mathrm{Z})}`, a maximally entangling ZZPhase - ESWAP : :math:`\alpha \mapsto e^{-\frac12 i\pi\alpha \cdot \mathrm{SWAP}} = \left[ \begin{array}{cccc} e^{-\frac12 i \pi\alpha} & 0 & 0 & 0 \\ 0 & \cos\frac{\pi\alpha}{2} & -i\sin\frac{\pi\alpha}{2} & 0 \\ 0 & -i\sin\frac{\pi\alpha}{2} & \cos\frac{\pi\alpha}{2} & 0 \\ 0 & 0 & 0 & e^{-\frac12 i \pi\alpha} \end{array} \right]` + ESWAP : :math:`\\alpha \\mapsto e^{-\\frac12 i\\pi\\alpha \\cdot \\mathrm{SWAP}} = \\left[ \\begin{array}{cccc} e^{-\\frac12 i \\pi\\alpha} & 0 & 0 & 0 \\\\ 0 & \\cos\\frac{\\pi\\alpha}{2} & -i\\sin\\frac{\\pi\\alpha}{2} & 0 \\\\ 0 & -i\\sin\\frac{\\pi\\alpha}{2} & \\cos\\frac{\\pi\\alpha}{2} & 0 \\\\ 0 & 0 & 0 & e^{-\\frac12 i \\pi\\alpha} \\end{array} \\right]` - FSim : :math:`(\alpha, \beta) \mapsto \left[ \begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & \cos \pi\alpha & -i\sin \pi\alpha & 0 \\ 0 & -i\sin \pi\alpha & \cos \pi\alpha & 0 \\ 0 & 0 & 0 & e^{-i\pi\beta} \end{array} \right]` + FSim : :math:`(\\alpha, \\beta) \\mapsto \\left[ \\begin{array}{cccc} 1 & 0 & 0 & 0 \\\\ 0 & \\cos \\pi\\alpha & -i\\sin \\pi\\alpha & 0 \\\\ 0 & -i\\sin \\pi\\alpha & \\cos \\pi\\alpha & 0 \\\\ 0 & 0 & 0 & e^{-i\\pi\\beta} \\end{array} \\right]` - Sycamore : :math:`\mathrm{FSim}(\frac12, \frac16)` + Sycamore : :math:`\\mathrm{FSim}(\\frac12, \\frac16)` - ISWAPMax : :math:`\mathrm{ISWAP}(1) = \left[ \begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & 0 & i & 0 \\ 0 & i & 0 & 0 \\ 0 & 0 & 0 & 1 \end{array} \right]` + ISWAPMax : :math:`\\mathrm{ISWAP}(1) = \\left[ \\begin{array}{cccc} 1 & 0 & 0 & 0 \\\\ 0 & 0 & i & 0 \\\\ 0 & i & 0 & 0 \\\\ 0 & 0 & 0 & 1 \\end{array} \\right]` ClassicalTransform : A general classical operation where all inputs are also outputs @@ -3240,104 +3298,107 @@ class OpType: DiagonalBox : A box for synthesising a diagonal unitary matrix into a sequence of multiplexed-Rz gates """ - BRIDGE: typing.ClassVar[OpType] # value = + AAMS: typing.ClassVar[OpType] # value = + BRIDGE: typing.ClassVar[OpType] # value = Barrier: typing.ClassVar[OpType] # value = Branch: typing.ClassVar[OpType] # value = - CCX: typing.ClassVar[OpType] # value = - CH: typing.ClassVar[OpType] # value = - CRx: typing.ClassVar[OpType] # value = - CRy: typing.ClassVar[OpType] # value = - CRz: typing.ClassVar[OpType] # value = - CS: typing.ClassVar[OpType] # value = - CSWAP: typing.ClassVar[OpType] # value = - CSX: typing.ClassVar[OpType] # value = - CSXdg: typing.ClassVar[OpType] # value = - CSdg: typing.ClassVar[OpType] # value = - CU1: typing.ClassVar[OpType] # value = - CU3: typing.ClassVar[OpType] # value = - CV: typing.ClassVar[OpType] # value = - CVdg: typing.ClassVar[OpType] # value = - CX: typing.ClassVar[OpType] # value = - CY: typing.ClassVar[OpType] # value = - CZ: typing.ClassVar[OpType] # value = - CircBox: typing.ClassVar[OpType] # value = - ClassicalExpBox: typing.ClassVar[OpType] # value = + CCX: typing.ClassVar[OpType] # value = + CH: typing.ClassVar[OpType] # value = + CRx: typing.ClassVar[OpType] # value = + CRy: typing.ClassVar[OpType] # value = + CRz: typing.ClassVar[OpType] # value = + CS: typing.ClassVar[OpType] # value = + CSWAP: typing.ClassVar[OpType] # value = + CSX: typing.ClassVar[OpType] # value = + CSXdg: typing.ClassVar[OpType] # value = + CSdg: typing.ClassVar[OpType] # value = + CU1: typing.ClassVar[OpType] # value = + CU3: typing.ClassVar[OpType] # value = + CV: typing.ClassVar[OpType] # value = + CVdg: typing.ClassVar[OpType] # value = + CX: typing.ClassVar[OpType] # value = + CY: typing.ClassVar[OpType] # value = + CZ: typing.ClassVar[OpType] # value = + CircBox: typing.ClassVar[OpType] # value = + ClassicalExpBox: typing.ClassVar[OpType] # value = ClassicalTransform: typing.ClassVar[OpType] # value = - CnRy: typing.ClassVar[OpType] # value = - CnX: typing.ClassVar[OpType] # value = - CnY: typing.ClassVar[OpType] # value = - CnZ: typing.ClassVar[OpType] # value = - Conditional: typing.ClassVar[OpType] # value = - ConjugationBox: typing.ClassVar[OpType] # value = + CnRy: typing.ClassVar[OpType] # value = + CnX: typing.ClassVar[OpType] # value = + CnY: typing.ClassVar[OpType] # value = + CnZ: typing.ClassVar[OpType] # value = + Conditional: typing.ClassVar[OpType] # value = + ConjugationBox: typing.ClassVar[OpType] # value = CopyBits: typing.ClassVar[OpType] # value = - CustomGate: typing.ClassVar[OpType] # value = - DiagonalBox: typing.ClassVar[OpType] # value = - DummyBox: typing.ClassVar[OpType] # value = - ECR: typing.ClassVar[OpType] # value = - ESWAP: typing.ClassVar[OpType] # value = - ExpBox: typing.ClassVar[OpType] # value = + CustomGate: typing.ClassVar[OpType] # value = + DiagonalBox: typing.ClassVar[OpType] # value = + DummyBox: typing.ClassVar[OpType] # value = + ECR: typing.ClassVar[OpType] # value = + ESWAP: typing.ClassVar[OpType] # value = + ExpBox: typing.ClassVar[OpType] # value = ExplicitModifier: typing.ClassVar[OpType] # value = ExplicitPredicate: typing.ClassVar[OpType] # value = - FSim: typing.ClassVar[OpType] # value = + FSim: typing.ClassVar[OpType] # value = + GPI: typing.ClassVar[OpType] # value = + GPI2: typing.ClassVar[OpType] # value = Goto: typing.ClassVar[OpType] # value = H: typing.ClassVar[OpType] # value = - ISWAP: typing.ClassVar[OpType] # value = - ISWAPMax: typing.ClassVar[OpType] # value = + ISWAP: typing.ClassVar[OpType] # value = + ISWAPMax: typing.ClassVar[OpType] # value = Label: typing.ClassVar[OpType] # value = - Measure: typing.ClassVar[OpType] # value = + Measure: typing.ClassVar[OpType] # value = MultiBit: typing.ClassVar[OpType] # value = - MultiplexedRotationBox: typing.ClassVar[OpType] # value = - MultiplexedTensoredU2Box: typing.ClassVar[OpType] # value = - MultiplexedU2Box: typing.ClassVar[OpType] # value = - MultiplexorBox: typing.ClassVar[OpType] # value = - NPhasedX: typing.ClassVar[OpType] # value = - PauliExpBox: typing.ClassVar[OpType] # value = - PauliExpCommutingSetBox: typing.ClassVar[OpType] # value = - PauliExpPairBox: typing.ClassVar[OpType] # value = + MultiplexedRotationBox: typing.ClassVar[OpType] # value = + MultiplexedTensoredU2Box: typing.ClassVar[OpType] # value = + MultiplexedU2Box: typing.ClassVar[OpType] # value = + MultiplexorBox: typing.ClassVar[OpType] # value = + NPhasedX: typing.ClassVar[OpType] # value = + PauliExpBox: typing.ClassVar[OpType] # value = + PauliExpCommutingSetBox: typing.ClassVar[OpType] # value = + PauliExpPairBox: typing.ClassVar[OpType] # value = Phase: typing.ClassVar[OpType] # value = - PhasePolyBox: typing.ClassVar[OpType] # value = - PhasedISWAP: typing.ClassVar[OpType] # value = - PhasedX: typing.ClassVar[OpType] # value = - QControlBox: typing.ClassVar[OpType] # value = + PhasePolyBox: typing.ClassVar[OpType] # value = + PhasedISWAP: typing.ClassVar[OpType] # value = + PhasedX: typing.ClassVar[OpType] # value = + QControlBox: typing.ClassVar[OpType] # value = RangePredicate: typing.ClassVar[OpType] # value = - Reset: typing.ClassVar[OpType] # value = + Reset: typing.ClassVar[OpType] # value = Rx: typing.ClassVar[OpType] # value = Ry: typing.ClassVar[OpType] # value = Rz: typing.ClassVar[OpType] # value = S: typing.ClassVar[OpType] # value = - SWAP: typing.ClassVar[OpType] # value = + SWAP: typing.ClassVar[OpType] # value = SX: typing.ClassVar[OpType] # value = SXdg: typing.ClassVar[OpType] # value = Sdg: typing.ClassVar[OpType] # value = SetBits: typing.ClassVar[OpType] # value = - StatePreparationBox: typing.ClassVar[OpType] # value = + StatePreparationBox: typing.ClassVar[OpType] # value = Stop: typing.ClassVar[OpType] # value = - Sycamore: typing.ClassVar[OpType] # value = + Sycamore: typing.ClassVar[OpType] # value = T: typing.ClassVar[OpType] # value = - TK1: typing.ClassVar[OpType] # value = - TK2: typing.ClassVar[OpType] # value = + TK1: typing.ClassVar[OpType] # value = + TK2: typing.ClassVar[OpType] # value = Tdg: typing.ClassVar[OpType] # value = - TermSequenceBox: typing.ClassVar[OpType] # value = - ToffoliBox: typing.ClassVar[OpType] # value = + TermSequenceBox: typing.ClassVar[OpType] # value = + ToffoliBox: typing.ClassVar[OpType] # value = U1: typing.ClassVar[OpType] # value = U2: typing.ClassVar[OpType] # value = U3: typing.ClassVar[OpType] # value = - Unitary1qBox: typing.ClassVar[OpType] # value = - Unitary2qBox: typing.ClassVar[OpType] # value = - Unitary3qBox: typing.ClassVar[OpType] # value = + Unitary1qBox: typing.ClassVar[OpType] # value = + Unitary2qBox: typing.ClassVar[OpType] # value = + Unitary3qBox: typing.ClassVar[OpType] # value = V: typing.ClassVar[OpType] # value = Vdg: typing.ClassVar[OpType] # value = WASM: typing.ClassVar[OpType] # value = X: typing.ClassVar[OpType] # value = - XXPhase: typing.ClassVar[OpType] # value = - XXPhase3: typing.ClassVar[OpType] # value = + XXPhase: typing.ClassVar[OpType] # value = + XXPhase3: typing.ClassVar[OpType] # value = Y: typing.ClassVar[OpType] # value = - YYPhase: typing.ClassVar[OpType] # value = + YYPhase: typing.ClassVar[OpType] # value = 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': , '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': , 'CnRy': , 'CnX': , 'CnY': , 'CnZ': , 'ZZMax': , 'ESWAP': , 'FSim': , 'Sycamore': , 'ISWAPMax': , 'ClassicalTransform': , 'WASM': , 'SetBits': , 'CopyBits': , 'RangePredicate': , 'ExplicitPredicate': , 'ExplicitModifier': , 'MultiBit': , 'ClassicalExpBox': , 'MultiplexorBox': , 'MultiplexedRotationBox': , 'MultiplexedU2Box': , 'MultiplexedTensoredU2Box': , 'StatePreparationBox': , 'DiagonalBox': } - noop: 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': , 'CnRy': , 'CnX': , 'CnY': , 'CnZ': , 'ZZMax': , 'ESWAP': , 'FSim': , 'Sycamore': , 'ISWAPMax': , 'ClassicalTransform': , 'WASM': , 'SetBits': , 'CopyBits': , 'RangePredicate': , 'ExplicitPredicate': , 'ExplicitModifier': , 'MultiBit': , 'ClassicalExpBox': , 'MultiplexorBox': , 'MultiplexedRotationBox': , 'MultiplexedU2Box': , 'MultiplexedTensoredU2Box': , 'StatePreparationBox': , 'DiagonalBox': } + noop: typing.ClassVar[OpType] # value = @staticmethod def from_name(arg0: str) -> OpType: """ @@ -3383,7 +3444,7 @@ class PauliExpBox(Op): """ def __init__(self, paulis: typing.Sequence[pytket._tket.pauli.Pauli], t: sympy.Expr | float, cx_config_type: CXConfigType = CXConfigType.Tree) -> None: """ - Construct :math:`e^{-\frac12 i \pi t \sigma_0 \otimes \sigma_1 \otimes \cdots}` from Pauli operators :math:`\sigma_i \in \{I,X,Y,Z\}` and a parameter :math:`t`. + Construct :math:`e^{-\\frac12 i \\pi t \\sigma_0 \\otimes \\sigma_1 \\otimes \\cdots}` from Pauli operators :math:`\\sigma_i \\in \\{I,X,Y,Z\\}` and a parameter :math:`t`. """ def get_circuit(self) -> Circuit: """ @@ -3407,7 +3468,7 @@ class PauliExpCommutingSetBox(Op): """ def __init__(self, pauli_gadgets: typing.Sequence[tuple[typing.Sequence[pytket._tket.pauli.Pauli], sympy.Expr | float]], cx_config_type: CXConfigType = CXConfigType.Tree) -> None: """ - Construct a set of necessarily commuting Pauli exponentials of the form :math:`e^{-\frac12 i \pi t_j \sigma_0 \otimes \sigma_1 \otimes \cdots}` from Pauli operator strings :math:`\sigma_i \in \{I,X,Y,Z\}` and parameters :math:`t_j, j \in \{0, 1, \cdots \}`. + Construct a set of necessarily commuting Pauli exponentials of the form :math:`e^{-\\frac12 i \\pi t_j \\sigma_0 \\otimes \\sigma_1 \\otimes \\cdots}` from Pauli operator strings :math:`\\sigma_i \\in \\{I,X,Y,Z\\}` and parameters :math:`t_j, j \\in \\{0, 1, \\cdots \\}`. """ def get_circuit(self) -> Circuit: """ @@ -3429,7 +3490,7 @@ class PauliExpPairBox(Op): """ def __init__(self, paulis0: typing.Sequence[pytket._tket.pauli.Pauli], t0: sympy.Expr | float, paulis1: typing.Sequence[pytket._tket.pauli.Pauli], t1: sympy.Expr | float, cx_config_type: CXConfigType = CXConfigType.Tree) -> None: """ - Construct a pair of Pauli exponentials of the form :math:`e^{-\frac12 i \pi t_j \sigma_0 \otimes \sigma_1 \otimes \cdots}` from Pauli operator strings :math:`\sigma_i \in \{I,X,Y,Z\}` and parameters :math:`t_j, j \in \{0,1\}`. + Construct a pair of Pauli exponentials of the form :math:`e^{-\\frac12 i \\pi t_j \\sigma_0 \\otimes \\sigma_1 \\otimes \\cdots}` from Pauli operator strings :math:`\\sigma_i \\in \\{I,X,Y,Z\\}` and parameters :math:`t_j, j \\in \\{0,1\\}`. """ def get_circuit(self) -> Circuit: """ @@ -3676,7 +3737,7 @@ class StabiliserAssertionBox(Op): """ class StatePreparationBox(Op): """ - A box for preparing quantum states using multiplexed-Ry and multiplexed-Rz gates + A box for preparing quantum states using multiplexed-Ry and multiplexed-Rz gates. Implementation based on Theorem 9 of arxiv.org/abs/quant-ph/0406176. The decomposed circuit has at most 2*(2^n-2) CX gates, and 2^n-2 CX gates if the coefficients are all real. """ def __init__(self, statevector: NDArray[numpy.complex128], is_inverse: bool = False, with_initial_reset: bool = False) -> None: """ @@ -3708,7 +3769,7 @@ class TermSequenceBox(Op): """ def __init__(self, pauli_gadgets: typing.Sequence[tuple[typing.Sequence[pytket._tket.pauli.Pauli], sympy.Expr | float]], synthesis_strategy: pytket._tket.transform.PauliSynthStrat = pytket._tket.transform.PauliSynthStrat.Sets, partitioning_strategy: pytket._tket.partition.PauliPartitionStrat = pytket._tket.partition.PauliPartitionStrat.CommutingSets, graph_colouring: pytket._tket.partition.GraphColourMethod = pytket._tket.partition.GraphColourMethod.Lazy, cx_config_type: CXConfigType = CXConfigType.Tree) -> None: """ - Construct a set of Pauli exponentials of the form :math:`e^{-\frac12 i \pi t_j \sigma_0 \otimes \sigma_1 \otimes \cdots}` from Pauli operator strings :math:`\sigma_i \in \{I,X,Y,Z\}` and parameters :math:`t_j, j \in \{0, 1, \cdots \}`. + Construct a set of Pauli exponentials of the form :math:`e^{-\\frac12 i \\pi t_j \\sigma_0 \\otimes \\sigma_1 \\otimes \\cdots}` from Pauli operator strings :math:`\\sigma_i \\in \\{I,X,Y,Z\\}` and parameters :math:`t_j, j \\in \\{0, 1, \\cdots \\}`. """ def get_circuit(self) -> Circuit: """ diff --git a/pytket/pytket/_tket/circuit_library.pyi b/pytket/pytket/_tket/circuit_library.pyi index 46fc68d76b..92d11c805b 100644 --- a/pytket/pytket/_tket/circuit_library.pyi +++ b/pytket/pytket/_tket/circuit_library.pyi @@ -2,7 +2,7 @@ from __future__ import annotations import pytket._tket.circuit import sympy import typing -__all__ = ['BRIDGE', 'BRIDGE_using_CX_0', 'BRIDGE_using_CX_1', 'C3X_normal_decomp', 'C4X_normal_decomp', 'CCX', 'CCX_modulo_phase_shift', 'CCX_normal_decomp', 'CH_using_CX', 'CRx_using_CX', 'CRx_using_TK2', 'CRy_using_CX', 'CRy_using_TK2', 'CRz_using_CX', 'CRz_using_TK2', 'CSWAP_using_CX', 'CSX_using_CX', 'CSXdg_using_CX', 'CS_using_CX', 'CSdg_using_CX', 'CU1_using_CX', 'CU1_using_TK2', 'CU3_using_CX', 'CV_using_CX', 'CVdg_using_CX', 'CX', 'CX_S_CX_reduced', 'CX_S_V_XC_reduced', 'CX_VS_CX_reduced', 'CX_V_CX_reduced', 'CX_V_S_XC_reduced', 'CX_XC_reduced', 'CX_using_ECR', 'CX_using_TK2', 'CX_using_XXPhase_0', 'CX_using_XXPhase_1', 'CX_using_ZZMax', 'CX_using_ZZPhase', 'CX_using_flipped_CX', 'CY_using_CX', 'CZ_using_CX', 'ECR_using_CX', 'ESWAP_using_CX', 'ESWAP_using_TK2', 'FSim_using_CX', 'FSim_using_TK2', 'H_CZ_H', 'ISWAP_using_CX', 'ISWAP_using_TK2', 'NPhasedX_using_PhasedX', 'PhasedISWAP_using_CX', 'PhasedISWAP_using_TK2', 'SWAP_using_CX_0', 'SWAP_using_CX_1', 'TK1_to_PhasedXRz', 'TK1_to_RzH', 'TK1_to_RzRx', 'TK1_to_RzSX', 'TK1_to_TK1', 'TK2_using_3xCX', 'TK2_using_CX', 'TK2_using_CX_and_swap', 'TK2_using_TK2_or_swap', 'TK2_using_ZZMax', 'TK2_using_ZZMax_and_swap', 'TK2_using_ZZPhase', 'TK2_using_ZZPhase_and_swap', 'TK2_using_normalised_TK2', 'X', 'X1_CX', 'XXPhase3_using_CX', 'XXPhase3_using_TK2', 'XXPhase_using_CX', 'XXPhase_using_TK2', 'YYPhase_using_CX', 'YYPhase_using_TK2', 'Z0_CX', 'ZZMax_using_CX', 'ZZPhase_using_CX', 'ZZPhase_using_TK2', 'approx_TK2_using_1xCX', 'approx_TK2_using_1xZZPhase', 'approx_TK2_using_2xCX', 'approx_TK2_using_2xZZPhase', 'ladder_down', 'ladder_down_2', 'ladder_up', 'two_Rz1'] +__all__ = ['BRIDGE', 'BRIDGE_using_CX_0', 'BRIDGE_using_CX_1', 'C3X_normal_decomp', 'C4X_normal_decomp', 'CCX', 'CCX_modulo_phase_shift', 'CCX_normal_decomp', 'CH_using_CX', 'CRx_using_CX', 'CRx_using_TK2', 'CRy_using_CX', 'CRy_using_TK2', 'CRz_using_CX', 'CRz_using_TK2', 'CSWAP_using_CX', 'CSX_using_CX', 'CSXdg_using_CX', 'CS_using_CX', 'CSdg_using_CX', 'CU1_using_CX', 'CU1_using_TK2', 'CU3_using_CX', 'CV_using_CX', 'CVdg_using_CX', 'CX', 'CX_S_CX_reduced', 'CX_S_V_XC_reduced', 'CX_VS_CX_reduced', 'CX_V_CX_reduced', 'CX_V_S_XC_reduced', 'CX_XC_reduced', 'CX_using_ECR', 'CX_using_TK2', 'CX_using_XXPhase_0', 'CX_using_XXPhase_1', 'CX_using_ZZMax', 'CX_using_ZZPhase', 'CX_using_flipped_CX', 'CY_using_CX', 'CZ_using_CX', 'ECR_using_CX', 'ESWAP_using_CX', 'ESWAP_using_TK2', 'FSim_using_CX', 'FSim_using_TK2', 'H_CZ_H', 'ISWAP_using_CX', 'ISWAP_using_TK2', 'NPhasedX_using_PhasedX', 'PhasedISWAP_using_CX', 'PhasedISWAP_using_TK2', 'SWAP_using_CX_0', 'SWAP_using_CX_1', 'TK1_to_PhasedXRz', 'TK1_to_RzH', 'TK1_to_RzRx', 'TK1_to_RzSX', 'TK1_to_TK1', 'TK2_using_3xCX', 'TK2_using_CX', 'TK2_using_CX_and_swap', 'TK2_using_TK2_or_swap', 'TK2_using_ZZMax', 'TK2_using_ZZMax_and_swap', 'TK2_using_ZZPhase', 'TK2_using_ZZPhase_and_swap', 'TK2_using_normalised_TK2', 'X', 'X1_CX', 'XXPhase3_using_CX', 'XXPhase3_using_TK2', 'XXPhase_using_CX', 'XXPhase_using_TK2', 'YYPhase_using_CX', 'YYPhase_using_TK2', 'Z0_CX', 'ZZMax_using_CX', 'ZZPhase_using_CX', 'ZZPhase_using_TK2', 'approx_TK2_using_1xCX', 'approx_TK2_using_1xZZPhase', 'approx_TK2_using_2xCX', 'approx_TK2_using_2xZZPhase', 'ladder_down', 'ladder_down_2', 'ladder_up'] def BRIDGE() -> pytket._tket.circuit.Circuit: """ Just a BRIDGE[0,1,2] gate @@ -357,7 +357,3 @@ def ladder_up() -> pytket._tket.circuit.Circuit: """ CCX[0,1,2]; CX[2,0]; CX[2,1] """ -def two_Rz1() -> pytket._tket.circuit.Circuit: - """ - A two-qubit circuit with an Rz(1) on each qubit - """ diff --git a/pytket/pytket/_tket/passes.pyi b/pytket/pytket/_tket/passes.pyi index 9f28980fab..22bc51873e 100644 --- a/pytket/pytket/_tket/passes.pyi +++ b/pytket/pytket/_tket/passes.pyi @@ -9,7 +9,7 @@ import pytket._tket.transform import pytket._tket.unit_id import sympy import typing -__all__ = ['AASRouting', 'Audit', 'BasePass', 'CNotSynthType', 'CXMappingPass', 'CliffordResynthesis', 'CliffordSimp', 'CnXPairwiseDecomposition', 'CommuteThroughMultis', 'ComposePhasePolyBoxes', 'ContextSimp', 'CustomPass', 'CustomRoutingPass', 'DecomposeArbitrarilyControlledGates', 'DecomposeBoxes', 'DecomposeClassicalExp', 'DecomposeMultiQubitsCX', 'DecomposeSingleQubitsTK1', 'DecomposeSwapsToCXs', 'DecomposeSwapsToCircuit', 'DecomposeTK2', 'Default', 'DefaultMappingPass', 'DelayMeasures', 'EulerAngleReduction', 'FlattenRegisters', 'FlattenRelabelRegistersPass', 'FullMappingPass', 'FullPeepholeOptimise', 'GlobalisePhasedX', 'GuidedPauliSimp', 'HamPath', 'KAKDecomposition', 'NaivePlacementPass', 'NormaliseTK2', 'OptimisePhaseGadgets', 'PauliExponentials', 'PauliSimp', 'PauliSquash', 'PeepholeOptimise2Q', 'PlacementPass', 'RebaseCustom', 'RebaseTket', 'Rec', 'RemoveBarriers', 'RemoveDiscarded', 'RemoveImplicitQubitPermutation', 'RemoveRedundancies', 'RenameQubitsPass', 'RepeatPass', 'RepeatUntilSatisfiedPass', 'RepeatWithMetricPass', 'RoundAngles', 'RoutingPass', 'SWAP', 'SafetyMode', 'SequencePass', 'SimplifyInitial', 'SimplifyMeasured', 'SquashCustom', 'SquashRzPhasedX', 'SquashTK1', 'SynthesiseHQS', 'SynthesiseOQC', 'SynthesiseTK', 'SynthesiseTket', 'SynthesiseUMD', 'ThreeQubitSquash', 'ZXGraphlikeOptimisation', 'ZZPhaseToRz'] +__all__ = ['AASRouting', 'Audit', 'BasePass', 'CNotSynthType', 'CXMappingPass', 'CliffordPushThroughMeasures', 'CliffordResynthesis', 'CliffordSimp', 'CnXPairwiseDecomposition', 'CommuteThroughMultis', 'ComposePhasePolyBoxes', 'ContextSimp', 'CustomPass', 'CustomRoutingPass', 'DecomposeArbitrarilyControlledGates', 'DecomposeBoxes', 'DecomposeClassicalExp', 'DecomposeMultiQubitsCX', 'DecomposeSingleQubitsTK1', 'DecomposeSwapsToCXs', 'DecomposeSwapsToCircuit', 'DecomposeTK2', 'Default', 'DefaultMappingPass', 'DelayMeasures', 'EulerAngleReduction', 'FlattenRegisters', 'FlattenRelabelRegistersPass', 'FullMappingPass', 'FullPeepholeOptimise', 'GlobalisePhasedX', 'GuidedPauliSimp', 'HamPath', 'KAKDecomposition', 'NaivePlacementPass', 'NormaliseTK2', 'OptimisePhaseGadgets', 'PauliExponentials', 'PauliSimp', 'PauliSquash', 'PeepholeOptimise2Q', 'PlacementPass', 'RebaseCustom', 'RebaseTket', 'Rec', 'RemoveBarriers', 'RemoveDiscarded', 'RemoveImplicitQubitPermutation', 'RemoveRedundancies', 'RenameQubitsPass', 'RepeatPass', 'RepeatUntilSatisfiedPass', 'RepeatWithMetricPass', 'RoundAngles', 'RoutingPass', 'SWAP', 'SafetyMode', 'SequencePass', 'SimplifyInitial', 'SimplifyMeasured', 'SquashCustom', 'SquashRzPhasedX', 'SquashTK1', 'SynthesiseOQC', 'SynthesiseTK', 'SynthesiseTket', 'SynthesiseUMD', 'ThreeQubitSquash', 'ZXGraphlikeOptimisation', 'ZZPhaseToRz'] class BasePass: """ Base class for passes. @@ -42,7 +42,7 @@ class BasePass: :return: True if pass modified the circuit, else False """ @typing.overload - def apply(self, circuit: pytket._tket.circuit.Circuit, before_apply: typing.Callable[[pytket._tket.predicates.CompilationUnit, object], None], after_apply: typing.Callable[[pytket._tket.predicates.CompilationUnit, object], None]) -> bool: + def apply(self, circuit: pytket._tket.circuit.Circuit, before_apply: typing.Callable[[pytket._tket.predicates.CompilationUnit, typing.Any], None], after_apply: typing.Callable[[pytket._tket.predicates.CompilationUnit, typing.Any], None]) -> bool: """ Apply to a :py:class:`Circuit` in-place and invoke callbacks for all nested passes. @@ -123,7 +123,7 @@ class RepeatUntilSatisfiedPass(BasePass): """ Construct from a compilation pass and a user-defined function from :py:class:`Circuit` to `bool`. """ - def __str__(self) -> str: + def __str__(self: BasePass) -> str: ... def get_pass(self) -> BasePass: """ @@ -141,7 +141,7 @@ class RepeatWithMetricPass(BasePass): """ Construct from a compilation pass and a metric function. """ - def __str__(self) -> str: + def __str__(self: BasePass) -> str: ... def get_metric(self) -> typing.Callable[[pytket._tket.circuit.Circuit], int]: """ @@ -192,11 +192,15 @@ class SequencePass(BasePass): """ A sequence of compilation passes. """ - def __init__(self, pass_list: typing.Sequence[BasePass]) -> None: + def __init__(self, pass_list: typing.Sequence[BasePass], strict: bool = True) -> None: """ Construct from a list of compilation passes arranged in order of application. + + :param pass_list: sequence of passes + :param strict: if True (the default), check that all postconditions and preconditions of the passes in the sequence are compatible and raise an exception if not. + :return: a pass that applies the sequence """ - def __str__(self) -> str: + def __str__(self: BasePass) -> str: ... def get_sequence(self) -> list[BasePass]: """ @@ -212,7 +216,7 @@ def AASRouting(arc: pytket._tket.architecture.Architecture, **kwargs: Any) -> Ba NB: The circuit needs to have at most as many qubits as the architecture has nodes. The resulting circuit will always have the same number of qubits as the architecture has nodes, even if the input circuit had fewer. :param arc: target architecture - :param \**kwargs: parameters for routing (described above) + :param \\**kwargs: parameters for routing (described above) :return: a pass to perform the remapping """ def CXMappingPass(arc: pytket._tket.architecture.Architecture, placer: pytket._tket.placement.Placement, **kwargs: Any) -> BasePass: @@ -221,9 +225,14 @@ def CXMappingPass(arc: pytket._tket.architecture.Architecture, placer: pytket._t :param arc: The Architecture used for connectivity information. :param placer: The placement used for relabelling. - :param \**kwargs: Parameters for routing: (bool)directed_cx=false, (bool)delay_measures=true + :param \\**kwargs: Parameters for routing: (bool)directed_cx=false, (bool)delay_measures=true :return: a pass to perform the remapping """ +def CliffordPushThroughMeasures() -> BasePass: + """ + An optimisation pass that resynthesise a Clifford subcircuit before end of circuit Measurement operations by implementing the action of the Clifford as a mutual diagonalisation circuit and a permutation on output measurements realised as a series of classical operations. + : return: a pass to simplify end of circuit Clifford gates. + """ def CliffordResynthesis(transform: typing.Callable[[pytket._tket.circuit.Circuit], pytket._tket.circuit.Circuit] | None = None, allow_swaps: bool = True) -> BasePass: """ An optimisation pass that resynhesises all Clifford subcircuits and then applies some rewrite rules to simplify them further. @@ -253,7 +262,7 @@ def ComposePhasePolyBoxes(min_size: int = 0) -> BasePass: - (unsigned) min_size=0: minimal number of CX gates in each phase polynominal box: groups with a smaller number of CX gates are not affected by this transformation - :param \**kwargs: parameters for composition (described above) + :param \\**kwargs: parameters for composition (described above) :return: a pass to perform the composition """ def ContextSimp(allow_classical: bool = True, xcirc: pytket._tket.circuit.Circuit | None = None) -> BasePass: @@ -489,13 +498,13 @@ def PlacementPass(placer: pytket._tket.placement.Placement) -> BasePass: :return: a pass to relabel :py:class:`Circuit` Qubits to :py:class:`Architecture` Nodes """ @typing.overload -def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], cx_replacement: pytket._tket.circuit.Circuit, tk1_replacement: typing.Callable[[typing.Union[sympy.Expr, float], typing.Union[sympy.Expr, float], typing.Union[sympy.Expr, float]], pytket._tket.circuit.Circuit]) -> BasePass: +def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], cx_replacement: pytket._tket.circuit.Circuit, tk1_replacement: typing.Callable[[sympy.Expr | float, sympy.Expr | float, sympy.Expr | float], pytket._tket.circuit.Circuit]) -> BasePass: """ Construct a custom rebase pass, given user-defined rebases for TK1 and CX. This pass: 1. decomposes multi-qubit gates not in the set of gate types `gateset` to CX gates; 2. if CX is not in `gateset`, replaces CX gates with `cx_replacement`; - 3. converts any single-qubit gates not in the gate type set to the form :math:`\mathrm{Rz}(a)\mathrm{Rx}(b)\mathrm{Rz}(c)` (in matrix-multiplication order, i.e. reverse order in the circuit); + 3. converts any single-qubit gates not in the gate type set to the form :math:`\\mathrm{Rz}(a)\\mathrm{Rx}(b)\\mathrm{Rz}(c)` (in matrix-multiplication order, i.e. reverse order in the circuit); 4. applies the `tk1_replacement` function to each of these triples :math:`(a,b,c)` to generate replacement circuits. :param gateset: the allowed operations in the rebased circuit (in addition, Measure, Reset and Collapse operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) @@ -504,7 +513,7 @@ def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], cx_replacement: pytk :return: a pass that rebases to the given gate set (possibly including conditional and phase operations, and Measure, Reset and Collapse) """ @typing.overload -def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], tk2_replacement: typing.Callable[[typing.Union[sympy.Expr, float], typing.Union[sympy.Expr, float], typing.Union[sympy.Expr, float]], pytket._tket.circuit.Circuit], tk1_replacement: typing.Callable[[typing.Union[sympy.Expr, float], typing.Union[sympy.Expr, float], typing.Union[sympy.Expr, float]], pytket._tket.circuit.Circuit]) -> BasePass: +def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], tk2_replacement: typing.Callable[[sympy.Expr | float, sympy.Expr | float, sympy.Expr | float], pytket._tket.circuit.Circuit], tk1_replacement: typing.Callable[[sympy.Expr | float, sympy.Expr | float, sympy.Expr | float], pytket._tket.circuit.Circuit]) -> BasePass: """ Construct a custom rebase pass, given user-defined rebases for TK1 and TK2. This pass: @@ -550,10 +559,10 @@ def RenameQubitsPass(qubit_map: dict[pytket._tket.unit_id.Qubit, pytket._tket.un """ def RoundAngles(n: int, only_zeros: bool = False) -> BasePass: """ - Round angles to the nearest :math:`\pi / 2^n`. + Round angles to the nearest :math:`\\pi / 2^n`. :param n: precision parameter, must be >= 0 and < 32 - :param only_zeros: if True, only round angles less than :math:`\pi / 2^{n+1}` to zero, leave other angles alone (default False) + :param only_zeros: if True, only round angles less than :math:`\\pi / 2^{n+1}` to zero, leave other angles alone (default False) """ def RoutingPass(arc: pytket._tket.architecture.Architecture) -> BasePass: """ @@ -575,7 +584,7 @@ def SimplifyMeasured() -> BasePass: """ A pass to replace all 'classical maps' followed by measure operations whose quantum output is discarded with classical operations following the measure. (A 'classical map' is a quantum operation that acts as a permutation of the computational basis states followed by a diagonal operation.) """ -def SquashCustom(singleqs: set[pytket._tket.circuit.OpType], tk1_replacement: typing.Callable[[typing.Union[sympy.Expr, float], typing.Union[sympy.Expr, float], typing.Union[sympy.Expr, float]], pytket._tket.circuit.Circuit], always_squash_symbols: bool = False) -> BasePass: +def SquashCustom(singleqs: set[pytket._tket.circuit.OpType], tk1_replacement: typing.Callable[[sympy.Expr | float, sympy.Expr | float, sympy.Expr | float], pytket._tket.circuit.Circuit], always_squash_symbols: bool = False) -> BasePass: """ Squash sequences of single qubit gates from the target gate set into an optimal form given by `tk1_replacement`. @@ -585,19 +594,15 @@ def SquashCustom(singleqs: set[pytket._tket.circuit.OpType], tk1_replacement: ty """ def SquashRzPhasedX() -> BasePass: """ - Squash single qubit gates into PhasedX and Rz gates. Commute Rz gates to the back if possible. + Squash single qubit gates into PhasedX and Rz gates. Also remove identity gates. Commute Rz gates to the back if possible. """ def SquashTK1() -> BasePass: """ Squash sequences of single-qubit gates to TK1 gates. """ -def SynthesiseHQS() -> BasePass: - """ - Optimises and converts a circuit consisting of CX and single-qubit gates into one containing only ZZMax, PhasedX, Rz and Phase. DEPRECATED: will be removed after pytket 1.25. - """ def SynthesiseOQC() -> BasePass: """ - Optimises and converts all gates to ECR, Rz, SX and Phase. + Optimises and converts all gates to ECR, Rz, SX and Phase. DEPRECATED: will be removed after pytket 1.28. """ def SynthesiseTK() -> BasePass: """ diff --git a/pytket/pytket/_tket/pauli.pyi b/pytket/pytket/_tket/pauli.pyi index a3ab039645..6b8bbb25f1 100644 --- a/pytket/pytket/_tket/pauli.pyi +++ b/pytket/pytket/_tket/pauli.pyi @@ -176,14 +176,14 @@ class QubitPauliString: :return: a list of :py:class:`Qubit`-to-:py:class:`Pauli` entries, represented as dicts. """ @typing.overload - def to_sparse_matrix(self) -> scipy.sparse.csc_matrix[numpy.complex128]: + def to_sparse_matrix(self) -> scipy.sparse.csc_matrix: """ Represents the sparse string as a dense string (without padding for extra qubits) and generates the matrix for the tensor. Uses the ILO-BE convention, so ``Qubit("a", 0)`` is more significant that ``Qubit("a", 1)`` and ``Qubit("b")`` for indexing into the matrix. :return: a sparse matrix corresponding to the operator """ @typing.overload - def to_sparse_matrix(self, n_qubits: int) -> scipy.sparse.csc_matrix[numpy.complex128]: + def to_sparse_matrix(self, n_qubits: int) -> scipy.sparse.csc_matrix: """ Represents the sparse string as a dense string over `n_qubits` qubits (sequentially indexed from 0 in the default register) and generates the matrix for the tensor. Uses the ILO-BE convention, so ``Qubit(0)`` is the most significant bit for indexing into the matrix. @@ -191,7 +191,7 @@ class QubitPauliString: :return: a sparse matrix corresponding to the operator """ @typing.overload - def to_sparse_matrix(self, qubits: typing.Sequence[pytket._tket.unit_id.Qubit]) -> scipy.sparse.csc_matrix[numpy.complex128]: + def to_sparse_matrix(self, qubits: typing.Sequence[pytket._tket.unit_id.Qubit]) -> scipy.sparse.csc_matrix: """ Represents the sparse string as a dense string and generates the matrix for the tensor. Orders qubits according to `qubits` (padding with identities if they are not in the sparse string), so ``qubits[0]`` is the most significant bit for indexing into the matrix. @@ -297,14 +297,14 @@ class QubitPauliTensor: :return: expectation value with respect to state """ @typing.overload - def to_sparse_matrix(self) -> scipy.sparse.csc_matrix[numpy.complex128]: + def to_sparse_matrix(self) -> scipy.sparse.csc_matrix: """ Represents the sparse string as a dense string (without padding for extra qubits) and generates the matrix for the tensor. Uses the ILO-BE convention, so ``Qubit("a", 0)`` is more significant that ``Qubit("a", 1)`` and ``Qubit("b")`` for indexing into the matrix. :return: a sparse matrix corresponding to the tensor """ @typing.overload - def to_sparse_matrix(self, n_qubits: int) -> scipy.sparse.csc_matrix[numpy.complex128]: + def to_sparse_matrix(self, n_qubits: int) -> scipy.sparse.csc_matrix: """ Represents the sparse string as a dense string over `n_qubits` qubits (sequentially indexed from 0 in the default register) and generates the matrix for the tensor. Uses the ILO-BE convention, so ``Qubit(0)`` is the most significant bit for indexing into the matrix. @@ -312,7 +312,7 @@ class QubitPauliTensor: :return: a sparse matrix corresponding to the operator """ @typing.overload - def to_sparse_matrix(self, qubits: typing.Sequence[pytket._tket.unit_id.Qubit]) -> scipy.sparse.csc_matrix[numpy.complex128]: + def to_sparse_matrix(self, qubits: typing.Sequence[pytket._tket.unit_id.Qubit]) -> scipy.sparse.csc_matrix: """ Represents the sparse string as a dense string and generates the matrix for the tensor. Orders qubits according to `qubits` (padding with identities if they are not in the sparse string), so ``qubits[0]`` is the most significant bit for indexing into the matrix. diff --git a/pytket/pytket/_tket/placement.pyi b/pytket/pytket/_tket/placement.pyi index 881e188fd5..fcebdfd927 100644 --- a/pytket/pytket/_tket/placement.pyi +++ b/pytket/pytket/_tket/placement.pyi @@ -19,7 +19,7 @@ class GraphPlacement(Placement): :param maximum_pattern_gates: The upper bound on the number of circuit gates used to construct the pattern graph for finding subgraph monomorphisms. :param maximum_pattern_depth: The upper bound on the circuit depth gates are added to the pattern graph to for finding subgraph monomorphisms. """ - def __repr__(self) -> str: + def __repr__(self: Placement) -> str: ... def modify_config(self, **kwargs: Any) -> None: """ @@ -37,7 +37,7 @@ class LinePlacement(Placement): :param maximum_line_gates: maximum number of gates in the circuit considered when constructing lines for assigning to the graph :param maximum_line_depth: maximum depth of circuit considered when constructing lines for assigning to the graph """ - def __repr__(self) -> str: + def __repr__(self: Placement) -> str: ... class NoiseAwarePlacement(Placement): """ @@ -56,7 +56,7 @@ class NoiseAwarePlacement(Placement): :param maximum_pattern_gates: The upper bound on the number of circuit gates used to construct the pattern graph for finding subgraph monomorphisms. :param maximum_pattern_depth: The upper bound on the circuit depth gates are added to the pattern graph to for finding subgraph monomorphisms. """ - def __repr__(self) -> str: + def __repr__(self: Placement) -> str: ... def modify_config(self, **kwargs: Any) -> None: """ diff --git a/pytket/pytket/_tket/transform.pyi b/pytket/pytket/_tket/transform.pyi index 7d11b4a9a8..a3b2e88c29 100644 --- a/pytket/pytket/_tket/transform.pyi +++ b/pytket/pytket/_tket/transform.pyi @@ -223,6 +223,11 @@ class Transform: Fast optimisation pass, performing basic simplifications. Works on any circuit, giving the result in TK1 and TK2 gates. Preserves connectivity of circuit. """ @staticmethod + def PushCliffordsThroughMeasures() -> Transform: + """ + Derives a new set of end-of-Circuit measurement operators by acting on end-of-Circuit measurements with a Clifford subcircuit. The new set of measurement operators is necessarily commuting and is implemented by adding a new mutual diagonalisation Clifford subcirciuit to the end of the Circuit and implementing the remaining diagonal measurement operators by measuring and permuting the output. + """ + @staticmethod def RebaseToCirq() -> Transform: """ Rebase from any gate set into PhasedX, Rz, CZ. @@ -298,7 +303,7 @@ class Transform: @staticmethod def round_angles(n: int, only_zeros: bool = False) -> Transform: """ - :param only_zeros: if True, only round angles less than :math:`\pi / 2^{n+1}` to zero, leave other angles alone (default False) + :param only_zeros: if True, only round angles less than :math:`\\pi / 2^{n+1}` to zero, leave other angles alone (default False) """ @staticmethod def sequence(sequence: typing.Sequence[Transform]) -> Transform: diff --git a/pytket/pytket/_tket/unit_id.pyi b/pytket/pytket/_tket/unit_id.pyi index beaf4ce078..9dbc8cf0ea 100644 --- a/pytket/pytket/_tket/unit_id.pyi +++ b/pytket/pytket/_tket/unit_id.pyi @@ -11,10 +11,12 @@ class Bit(UnitID): """ Construct Bit instance from JSON serializable list representation of the Bit. """ - def __and__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: + def __and__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: ... def __eq__(self, arg0: typing.Any) -> bool: ... + def __getstate__(self) -> tuple: + ... def __hash__(self) -> int: ... @typing.overload @@ -56,15 +58,17 @@ class Bit(UnitID): :param name: The readable name for the register :param index: The index vector """ - def __or__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: + def __or__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: ... - def __rand__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: + def __rand__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: ... - def __ror__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: + def __ror__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: ... - def __rxor__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: + def __rxor__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: + ... + def __setstate__(self, arg0: tuple) -> None: ... - def __xor__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: + def __xor__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.Bit, int]) -> pytket.circuit.logic_exp.BitLogicExp: ... def to_list(self) -> list: """ @@ -76,9 +80,9 @@ class BitRegister: """ Linear register of UnitID types. """ - def __add__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __add__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... - def __and__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __and__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... def __contains__(self, arg0: Bit) -> bool: ... @@ -88,7 +92,7 @@ class BitRegister: ... def __eq__(self, arg0: typing.Any) -> bool: ... - def __floordiv__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __floordiv__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... def __getitem__(self, arg0: int) -> Bit: ... @@ -105,33 +109,33 @@ class BitRegister: ... def __len__(self) -> int: ... - def __lshift__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __lshift__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... def __lt__(self, arg0: BitRegister) -> bool: ... - def __mul__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __mul__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... def __next__(self) -> Bit: ... - def __or__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __or__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... - def __pow__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __pow__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... - def __rand__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __rand__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... def __repr__(self) -> str: ... - def __ror__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __ror__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... - def __rshift__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __rshift__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... - def __rxor__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __rxor__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... def __str__(self) -> str: ... - def __sub__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __sub__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... - def __xor__(self, other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: + def __xor__(self: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int], other: typing.Union[pytket.circuit.logic_exp.LogicExp, pytket._tket.unit_id.BitRegister, int]) -> pytket.circuit.logic_exp.RegLogicExp: ... def to_list(self) -> list[Bit]: ... diff --git a/pytket/pytket/circuit/decompose_classical.py b/pytket/pytket/circuit/decompose_classical.py index 4929e5dffb..c61238f36c 100644 --- a/pytket/pytket/circuit/decompose_classical.py +++ b/pytket/pytket/circuit/decompose_classical.py @@ -37,10 +37,7 @@ BitRegister, Bit, ) -from pytket._tket.circuit import ( - Circuit, - OpType, -) +from pytket._tket.circuit import Circuit, ClassicalExpBox, Conditional, OpType from pytket.circuit.logic_exp import ( BitLogicExp, BitWiseOp, @@ -280,9 +277,11 @@ def _decompose_expressions(circ: Circuit) -> Tuple[Circuit, bool]: args = command.args kwargs = dict() if optype == OpType.Conditional: + assert isinstance(op, Conditional) bits = args[: op.width] # check if conditional on previously decomposed expression if len(bits) == 1 and bits[0] in replace_targets: + assert isinstance(bits[0], Bit) # this op should encode comparison and value assert op.value in (0, 1) replace_bit = replace_targets[bits[0]] @@ -299,8 +298,9 @@ def _decompose_expressions(circ: Circuit) -> Tuple[Circuit, bool]: if optype == OpType.RangePredicate: target = args[-1] + assert isinstance(target, Bit) newcirc.add_bit(target, reject_dups=False) - temp_reg = temp_reg_in_args(args) + temp_reg = temp_reg_in_args(args) # type: ignore # ensure predicate is reading from correct output register if temp_reg in replace_targets: assert temp_reg is not None @@ -312,12 +312,14 @@ def _decompose_expressions(circ: Circuit) -> Tuple[Circuit, bool]: replace_targets[target] = target elif optype == OpType.ClassicalExpBox: + assert isinstance(op, ClassicalExpBox) pred_exp = copy.deepcopy(op.get_exp()) n_out_bits = op.get_n_o() + op.get_n_io() # copied as it will be modified in place if isinstance(pred_exp, BitLogicExp): assert n_out_bits == 1 target = args[-1] + assert isinstance(target, Bit) bit_heap.push(target) comp_bit = bit_recursive_walk(pred_exp, kwargs) @@ -325,6 +327,7 @@ def _decompose_expressions(circ: Circuit) -> Tuple[Circuit, bool]: replace_targets[target] = comp_bit else: + assert isinstance(pred_exp, RegLogicExp) output_args = args[-n_out_bits:] if not all( arg.reg_name == output_args[0].reg_name for arg in output_args diff --git a/pytket/pytket/circuit_library/__init__.py b/pytket/pytket/circuit_library/__init__.py index 54a7302580..114cfe5020 100644 --- a/pytket/pytket/circuit_library/__init__.py +++ b/pytket/pytket/circuit_library/__init__.py @@ -35,7 +35,6 @@ CX_XC_reduced, SWAP_using_CX_0, SWAP_using_CX_1, - two_Rz1, X1_CX, Z0_CX, CCX_modulo_phase_shift, diff --git a/pytket/pytket/passes/script.py b/pytket/pytket/passes/script.py index 2949b166c9..cc97fe7ad6 100644 --- a/pytket/pytket/passes/script.py +++ b/pytket/pytket/passes/script.py @@ -42,7 +42,6 @@ RemoveRedundancies, SimplifyInitial, SimplifyMeasured, - SynthesiseHQS, SynthesiseTket, SynthesiseOQC, SynthesiseUMD, @@ -88,7 +87,6 @@ | simplify_initial | simplify_initial_no_classical | simplify_measured - | synthesise_hqs | synthesise_tket | synthesise_oqc | synthesise_umd @@ -132,7 +130,6 @@ simplify_initial: "SimplifyInitial" simplify_initial_no_classical: "SimplifyInitialNoClassical" simplify_measured: "SimplifyMeasured" -synthesise_hqs: "SynthesiseHQS" synthesise_tket: "SynthesiseTket" synthesise_oqc: "SynthesiseOQC" synthesise_umd: "SynthesiseUMD" @@ -301,9 +298,6 @@ def simplify_initial_no_classical(self, t: List) -> BasePass: def simplify_measured(self, t: List) -> BasePass: return SimplifyMeasured() - def synthesise_hqs(self, t: List) -> BasePass: - return SynthesiseHQS() - def synthesise_tket(self, t: List) -> BasePass: return SynthesiseTket() diff --git a/pytket/pytket/qasm/includes/_oqclib1_decls.py b/pytket/pytket/qasm/includes/_oqclib1_decls.py deleted file mode 100644 index 91b048eacf..0000000000 --- a/pytket/pytket/qasm/includes/_oqclib1_decls.py +++ /dev/null @@ -1,2 +0,0 @@ -"""DO NOT EDIT! GENERATED BY load_includes.py.""" -_INCLUDE_DECLS={'rz': {'args': ['lam'], 'name': 'rz'}, 'sx': {'args': [], 'name': 'sx'}, 'ecr': {'args': [], 'name': 'ecr'}} \ No newline at end of file diff --git a/pytket/pytket/qasm/includes/_oqclib1_defs.py b/pytket/pytket/qasm/includes/_oqclib1_defs.py deleted file mode 100644 index 62bb9b9bf8..0000000000 --- a/pytket/pytket/qasm/includes/_oqclib1_defs.py +++ /dev/null @@ -1,2 +0,0 @@ -"""DO NOT EDIT! GENERATED BY load_includes.py.""" -_INCLUDE_DEFS={'rz': {'definition': {'bits': [], 'commands': [], 'created_qubits': [], 'discarded_qubits': [], 'implicit_permutation': [[['q', [0]], ['q', [0]]]], 'phase': '0.0', 'qubits': [['q', [0]]]}, 'args': ['lam'], 'name': 'rz'}, 'sx': {'definition': {'bits': [], 'commands': [], 'created_qubits': [], 'discarded_qubits': [], 'implicit_permutation': [[['q', [0]], ['q', [0]]]], 'phase': '0.0', 'qubits': [['q', [0]]]}, 'args': [], 'name': 'sx'}, 'ecr': {'definition': {'bits': [], 'commands': [], 'created_qubits': [], 'discarded_qubits': [], 'implicit_permutation': [[['q', [0]], ['q', [0]]], [['q', [1]], ['q', [1]]]], 'phase': '0.0', 'qubits': [['q', [0]], ['q', [1]]]}, 'args': [], 'name': 'ecr'}} \ No newline at end of file diff --git a/pytket/pytket/qasm/includes/oqclib1.inc b/pytket/pytket/qasm/includes/oqclib1.inc deleted file mode 100644 index ef7ffe421f..0000000000 --- a/pytket/pytket/qasm/includes/oqclib1.inc +++ /dev/null @@ -1,34 +0,0 @@ -// file: oqclib1.inc - -// --- Primitives --- - -// 1-qubit Z-axis rotation -// NOTE: For HQS compiler / simulation, only one of these can be active at a -// time -opaque rz(lam) q; -// gate Rz(lam) q -//{ -// U(0,0,lam) q; -// } - -// 1-qubit rotation gate -// NOTE: For HQS compiler / simulation, only one of these can be active at a -// time -opaque sx() q; -// gate U1q(theta, phi) q -//{ -// U(theta, phi-pi/2, pi/2-phi) q; -// } - -// Unitary 2-qubit gate -// NOTE: For HQS compiler / simulation, only one of these can be active at a -// time -opaque ecr() q1, q2; -// gate ZZ() q1,q2 -//{ -// U1q(pi/2, pi/2) q2; -// CX q1, q2; -// Rz(-pi/2) q1; -// U1q(pi/2, 0) q2; -// U1q(pi/2, -pi/2) q2; -// } diff --git a/pytket/pytket/qasm/qasm.py b/pytket/pytket/qasm/qasm.py index 4aea237fc3..68847d5481 100644 --- a/pytket/pytket/qasm/qasm.py +++ b/pytket/pytket/qasm/qasm.py @@ -17,7 +17,6 @@ import re import uuid -# TODO: Output custom gates from collections import OrderedDict from importlib import import_module from itertools import chain, groupby @@ -81,7 +80,7 @@ create_logic_exp, ) from pytket.qasm.grammar import grammar -from pytket.passes import auto_rebase_pass, RemoveRedundancies +from pytket.passes import auto_rebase_pass, DecomposeBoxes, RemoveRedundancies from pytket.wasm import WasmFileHandler @@ -132,8 +131,6 @@ class QASMUnsupportedError(Exception): "cy": OpType.CY, "ch": OpType.CH, "csx": OpType.CSX, - "cs": OpType.CS, - "csdg": OpType.CSdg, "ccx": OpType.CCX, "c3x": OpType.CnX, "c4x": OpType.CnX, @@ -144,7 +141,6 @@ class QASMUnsupportedError(Exception): "barrier": OpType.Barrier, "swap": OpType.SWAP, "cswap": OpType.CSWAP, - "ecr": OpType.ECR, } PARAM_COMMANDS = { "p": OpType.U1, # alias. https://github.com/Qiskit/qiskit-terra/pull/4765 @@ -179,6 +175,8 @@ class QASMUnsupportedError(Exception): "iswapmax": OpType.ISWAPMax, "zzmax": OpType.ZZMax, "ecr": OpType.ECR, + "cs": OpType.CS, + "csdg": OpType.CSdg, } PARAM_EXTRA_COMMANDS = { @@ -1060,25 +1058,11 @@ def _filtered_qasm_str(qasm: str) -> str: return "\n".join(lines) -def circuit_to_qasm_str( - circ: Circuit, - header: str = "qelib1", - include_gate_defs: Optional[Set[str]] = None, - maxwidth: int = 32, -) -> str: - """Convert a Circuit to QASM and return the string. +def is_empty_customgate(op: Op) -> bool: + return op.type == OpType.CustomGate and op.get_circuit().n_gates == 0 # type: ignore - Classical bits in the pytket circuit must be singly-indexed. - Note that this will not account for implicit qubit permutations in the Circuit. - - :param circ: pytket circuit - :param header: qasm header (default "qelib1") - :param output_file: path to output qasm file - :param include_gate_defs: optional set of gates to include - :param maxwidth: maximum allowed width of classical registers (default 32) - :return: qasm string - """ +def check_can_convert_circuit(circ: Circuit, header: str, maxwidth: int) -> None: if any( circ.n_gates_of_type(typ) for typ in ( @@ -1100,10 +1084,42 @@ def circuit_to_qasm_str( f"Circuit contains a classical register larger than {maxwidth}: try " "setting the `maxwidth` parameter to a higher value." ) + for cmd in circ: + if is_empty_customgate(cmd.op) or ( + isinstance(cmd.op, Conditional) and is_empty_customgate(cmd.op.op) + ): + raise QASMUnsupportedError( + f"Empty CustomGates and opaque gates are not supported." + ) + + +def circuit_to_qasm_str( + circ: Circuit, + header: str = "qelib1", + include_gate_defs: Optional[Set[str]] = None, + maxwidth: int = 32, +) -> str: + """Convert a Circuit to QASM and return the string. + + Classical bits in the pytket circuit must be singly-indexed. + + Note that this will not account for implicit qubit permutations in the Circuit. + + :param circ: pytket circuit + :param header: qasm header (default "qelib1") + :param output_file: path to output qasm file + :param include_gate_defs: optional set of gates to include + :param maxwidth: maximum allowed width of classical registers (default 32) + :return: qasm string + """ + + check_can_convert_circuit(circ, header, maxwidth) qasm_writer = QasmWriter( circ.qubits, circ.bits, header, include_gate_defs, maxwidth ) - for command in circ: + circ1 = circ.copy() + DecomposeBoxes().apply(circ1) + for command in circ1: assert isinstance(command, Command) qasm_writer.add_op(command.op, command.args) return qasm_writer.finalize() @@ -1216,6 +1232,43 @@ def get_full_string(self) -> str: return "".join(self.strings.values()) +def make_params_str(params: Optional[List[Union[float, Expr]]]) -> str: + s = "" + if params is not None: + n_params = len(params) + s += "(" + for i in range(n_params): + reduced = True + try: + p: Union[float, Expr] = float(params[i]) + except TypeError: + reduced = False + p = params[i] + if i < n_params - 1: + if reduced: + s += "{}*pi,".format(p) + else: + s += "({})*pi,".format(p) + else: + if reduced: + s += "{}*pi)".format(p) + else: + s += "({})*pi)".format(p) + s += " " + return s + + +def make_args_str(args: List[UnitID]) -> str: + s = "" + for i in range(len(args)): + s += f"{args[i]}" + if i < len(args) - 1: + s += "," + else: + s += ";\n" + return s + + class QasmWriter: """ Helper class for converting a sequence of TKET Commands to QASM, and retrieving the @@ -1237,6 +1290,8 @@ def __init__( self.include_module_gates.update( _load_include_module(header, False, True).keys() ) + self.prefix = "" + self.gatedefs = "" self.strings = LabelledStringList() # Record of `RangePredicate` operations that set a "scratch" bit to 0 or 1 @@ -1258,9 +1313,7 @@ def __init__( self.include_gate_defs = self.include_module_gates self.include_gate_defs.update(NOPARAM_EXTRA_COMMANDS.keys()) self.include_gate_defs.update(PARAM_EXTRA_COMMANDS.keys()) - self.strings.add_string( - 'OPENQASM 2.0;\ninclude "{}.inc";\n\n'.format(header) - ) + self.prefix = 'OPENQASM 2.0;\ninclude "{}.inc";\n\n'.format(header) self.qregs = _retrieve_registers(cast(list[UnitID], qubits), QubitRegister) self.cregs = _retrieve_registers(cast(list[UnitID], bits), BitRegister) for reg in self.qregs.values(): @@ -1288,70 +1341,46 @@ def __init__( self.qregs = {} def write_params(self, params: Optional[List[Union[float, Expr]]]) -> None: - if params is not None: - n_params = len(params) - self.strings.add_string("(") - for i in range(n_params): - reduced = True - try: - p: Union[float, Expr] = float(params[i]) - except TypeError: - reduced = False - p = params[i] - if i < n_params - 1: - if reduced: - self.strings.add_string("{}*pi,".format(p)) - else: - self.strings.add_string("({})*pi,".format(p)) - else: - if reduced: - self.strings.add_string("{}*pi)".format(p)) - else: - self.strings.add_string("({})*pi)".format(p)) - self.strings.add_string(" ") + params_str = make_params_str(params) + self.strings.add_string(params_str) def write_args(self, args: List[UnitID]) -> None: - for i in range(len(args)): - self.strings.add_string(f"{args[i]}") - if i < len(args) - 1: - self.strings.add_string(",") - else: - self.strings.add_string(";\n") + args_str = make_args_str(args) + self.strings.add_string(args_str) - def write_gate_definition( + def make_gate_definition( self, n_qubits: int, opstr: str, optype: OpType, n_params: Optional[int] = None, - ) -> None: - self.strings.add_string("gate " + opstr + " ") + ) -> str: + s = "gate " + opstr + " " symbols: Optional[List[Symbol]] = None if n_params is not None: # need to add parameters to gate definition - self.strings.add_string("(") + s += "(" symbols = [Symbol("param" + str(index) + "/pi") for index in range(n_params)] # type: ignore symbols_header = [Symbol("param" + str(index)) for index in range(n_params)] # type: ignore for symbol in symbols_header[:-1]: - self.strings.add_string(symbol.name + ", ") - self.strings.add_string(symbols_header[-1].name + ") ") + s += symbol.name + ", " + s += symbols_header[-1].name + ") " # add qubits to gate definition qubit_args = [ Qubit(opstr + "q" + str(index)) for index in list(range(n_qubits)) ] for qb in qubit_args[:-1]: - self.strings.add_string(str(qb) + ",") - self.strings.add_string(str(qubit_args[-1]) + " {\n") + s += str(qb) + "," + s += str(qubit_args[-1]) + " {\n" # get rebased circuit for constructing qasm gate_circ = _get_gate_circuit(optype, qubit_args, symbols) # write circuit to qasm - self.strings.add_string( - circuit_to_qasm_str( - gate_circ, self.header, self.include_gate_defs, self.maxwidth - ) + s += circuit_to_qasm_str( + gate_circ, self.header, self.include_gate_defs, self.maxwidth ) - self.strings.add_string("}\n") + s += "}\n" + return s def mark_as_written(self, written_variable: str) -> None: """Remove any references to the written-to variable in `self.range_preds`, so @@ -1401,6 +1430,25 @@ def add_range_predicate(self, op: RangePredicateOp, args: List[Bit]) -> None: # list if it is.) self.range_preds.append((variable, comparator, value, dest_bit, label)) + def condition_string(self, op: Conditional, variable: str) -> str: + # Check whether the variable is actually in `self.range_preds`. + hits = [ + (real_variable, comparator, value, label) + for (real_variable, comparator, value, dest_bit, label) in self.range_preds + if dest_bit == variable + ] + if not hits: + return f"if({variable}=={op.value}) " + else: + assert len(hits) == 1 + real_variable, comparator, value, label = hits[0] + self.strings.del_string(label) + if op.value == 1: + return f"if({real_variable}{comparator}{value}) " + else: + assert op.value == 0 + return f"if({real_variable}{_negate_comparator(comparator)}{value}) " + def add_conditional(self, op: Conditional, args: List[UnitID]) -> None: bits = args[: op.width] control_bit = bits[0] @@ -1425,25 +1473,8 @@ def add_conditional(self, op: Conditional, args: List[UnitID]) -> None: if op.op.type == OpType.Phase: # Conditional phase is ignored. return - # Check whether the variable is actually in `self.range_preds`. - hits = [ - (real_variable, comparator, value, label) - for (real_variable, comparator, value, dest_bit, label) in self.range_preds - if dest_bit == variable - ] - if not hits: - self.strings.add_string(f"if({variable}=={op.value}) ") - else: - assert len(hits) == 1 - real_variable, comparator, value, label = hits[0] - self.strings.del_string(label) - if op.value == 1: - self.strings.add_string(f"if({real_variable}{comparator}{value}) ") - else: - assert op.value == 0 - self.strings.add_string( - f"if({real_variable}{_negate_comparator(comparator)}{value}) " - ) + condstr = self.condition_string(op, variable) + self.strings.add_string(condstr) self.add_op(op.op, args[op.width :]) def add_set_bits(self, op: SetBitsOp, args: List[Bit]) -> None: @@ -1534,32 +1565,6 @@ def add_measure(self, args: List[UnitID]) -> None: self.strings.add_string(f"measure {args[0]} -> {args[1]};\n") self.mark_as_written(f"{args[1]}") - def add_custom_gate(self, op: CustomGate, args: List[UnitID]) -> None: - if op.gate.name not in self.include_gate_defs: - # unroll custom gate - gate_circ = op.get_circuit() - if gate_circ.n_gates == 0: - raise QASMUnsupportedError( - f"CustomGate {op.gate.name} has empty definition." - " Empty CustomGates and opaque gates are not supported." - ) - gate_circ.rename_units(dict(zip(gate_circ.qubits, args))) - gate_circ.symbol_substitution(dict(zip(op.gate.args, op.params))) - self.strings.add_string( - circuit_to_qasm_str( - gate_circ, self.header, self.include_gate_defs, self.maxwidth - ) - ) - else: - opstr = op.gate.name - if opstr not in self.include_gate_defs: - raise QASMUnsupportedError( - "Gate of type {} is not supported in conversion.".format(opstr) - ) - self.strings.add_string(opstr) - self.write_params(op.params) - self.write_args(args) - def add_zzphase(self, param: Union[float, Expr], args: List[UnitID]) -> None: # as op.params returns reduced parameters, we can assume # that 0 <= param < 4 @@ -1594,26 +1599,28 @@ def add_gate_params(self, op: Op, args: List[UnitID]) -> None: self.write_params(params) self.write_args(args) - def add_extra_noparams(self, op: Op, args: List[UnitID]) -> None: + def add_extra_noparams(self, op: Op, args: List[UnitID]) -> Tuple[str, str]: optype = op.type opstr = _tk_to_qasm_extra_noparams[optype] + gatedefstr = "" if opstr not in self.added_gate_definitions: self.added_gate_definitions.add(opstr) - self.write_gate_definition(op.n_qubits, opstr, optype) - self.strings.add_string(opstr) - self.strings.add_string(" ") - self.write_args(args) + gatedefstr = self.make_gate_definition(op.n_qubits, opstr, optype) + mainstr = opstr + " " + make_args_str(args) + return gatedefstr, mainstr - def add_extra_params(self, op: Op, args: List[UnitID]) -> None: + def add_extra_params(self, op: Op, args: List[UnitID]) -> Tuple[str, str]: optype, params = _get_optype_and_params(op) assert params is not None opstr = _tk_to_qasm_extra_params[optype] + gatedefstr = "" if opstr not in self.added_gate_definitions: self.added_gate_definitions.add(opstr) - self.write_gate_definition(op.n_qubits, opstr, optype, len(params)) - self.strings.add_string(opstr) - self.write_params(params) - self.write_args(args) + gatedefstr = self.make_gate_definition( + op.n_qubits, opstr, optype, len(params) + ) + mainstr = opstr + make_params_str(params) + make_args_str(args) + return gatedefstr, mainstr def add_op(self, op: Op, args: List[UnitID]) -> None: optype, _params = _get_optype_and_params(op) @@ -1645,9 +1652,6 @@ def add_op(self, op: Op, args: List[UnitID]) -> None: self.add_wasm(op, cast(List[Bit], args)) elif optype == OpType.Measure: self.add_measure(args) - elif optype == OpType.CustomGate: - assert isinstance(op, CustomGate) - self.add_custom_gate(op, args) elif hqs_header(self.header) and optype == OpType.ZZPhase: # special handling for zzphase assert len(op.params) == 1 @@ -1666,16 +1670,24 @@ def add_op(self, op: Op, args: List[UnitID]) -> None: ): self.add_gate_params(op, args) elif optype in _tk_to_qasm_extra_noparams: - self.add_extra_noparams(op, args) + gatedefstr, mainstr = self.add_extra_noparams(op, args) + self.gatedefs += gatedefstr + self.strings.add_string(mainstr) elif optype in _tk_to_qasm_extra_params: - self.add_extra_params(op, args) + gatedefstr, mainstr = self.add_extra_params(op, args) + self.gatedefs += gatedefstr + self.strings.add_string(mainstr) else: raise QASMUnsupportedError( "Cannot print command of type: {}".format(op.get_name()) ) def finalize(self) -> str: - return _filtered_qasm_str(self.strings.get_full_string()) + return ( + self.prefix + + self.gatedefs + + _filtered_qasm_str(self.strings.get_full_string()) + ) def circuit_to_qasm_io( diff --git a/pytket/pytket/utils/symbolic.py b/pytket/pytket/utils/symbolic.py index 115fe1d7d4..26b839fadb 100644 --- a/pytket/pytket/utils/symbolic.py +++ b/pytket/pytket/utils/symbolic.py @@ -359,7 +359,7 @@ def circuit_to_symbolic_gates(circ: Circuit) -> Mul: continue args = com.args try: - targs = [qubit_map[q] for q in args] + targs = [qubit_map[q] for q in args] # type: ignore except KeyError as e: raise ValueError( f"Gates can only act on qubits. Operation {com} not valid." diff --git a/pytket/pytket/wasm/wasm.py b/pytket/pytket/wasm/wasm.py index bf4f57cd68..3c107523e4 100644 --- a/pytket/pytket/wasm/wasm.py +++ b/pytket/pytket/wasm/wasm.py @@ -158,6 +158,12 @@ def __init__(self, filepath: str, check_file: bool = True, int_size: int = 32): if t != self._int_type: supported_function = False + if ( + len(function_signatures[self._function_types[idx]]["return_types"]) + > 1 + ): + supported_function = False + if supported_function: self._functions[x] = ( len( diff --git a/pytket/setup.py b/pytket/setup.py index 4d991ff639..bd9ff5d8dd 100755 --- a/pytket/setup.py +++ b/pytket/setup.py @@ -184,8 +184,7 @@ def finalize_options(self): "Source": "https://github.com/CQCL/tket", "Tracker": "https://github.com/CQCL/tket/issues", }, - description="Python module for interfacing with the CQC tket library of quantum " - "software", + description="Quantum computing toolkit and interface to the TKET compiler", long_description=open("package.md", "r").read(), long_description_content_type="text/markdown", license="Apache 2", @@ -194,7 +193,7 @@ def finalize_options(self): "sympy ~=1.6", "numpy >=1.21.4, <2.0", "lark-parser ~=0.7", - "scipy >=1.7.2, <2.0", + "scipy ~=1.13", "networkx >= 2.8.8", "graphviz ~= 0.14", "jinja2 ~= 3.0", @@ -204,7 +203,7 @@ def finalize_options(self): ], extras_require={ "ZX": [ - "quimb ~= 1.5", + "quimb ~= 1.8", "autoray >= 0.6.1", ], }, diff --git a/pytket/tests/circuit_test.py b/pytket/tests/circuit_test.py index fd9f3e4813..19eb6157b9 100644 --- a/pytket/tests/circuit_test.py +++ b/pytket/tests/circuit_test.py @@ -324,6 +324,15 @@ def test_symbolic_circbox() -> None: assert not c_outer.is_symbolic() +def test_renaming_circbox_circuit() -> None: + c = Circuit(2).CX(0, 1) + cbox = CircBox(c) + d = Circuit(2).add_circbox(cbox, [0, 1]) + cbox.circuit_name = "test_name" + assert cbox.circuit_name == "test_name" + assert d.get_commands()[0].op.circuit_name == "test_name" # type: ignore + + def test_subst_4() -> None: # https://github.com/CQCL/tket/issues/219 m = fresh_symbol("m") @@ -529,8 +538,10 @@ def test_boxes() -> None: pauli_exps = [cmd.op for cmd in d if cmd.op.type == OpType.PauliExpBox] assert len(pauli_exps) == 1 - assert pauli_exps[0].get_paulis() == paulis - assert pauli_exps[0].get_phase() == Symbol("alpha") # type: ignore + pauli_exp = pauli_exps[0] + assert isinstance(pauli_exp, PauliExpBox) + assert pauli_exp.get_paulis() == paulis + assert pauli_exp.get_phase() == Symbol("alpha") # type: ignore boxes = (cbox, mbox, u2qbox, u3qbox, ebox, pbox, qcbox) assert all(box == box for box in boxes) @@ -1510,6 +1521,12 @@ def test_bad_circbox() -> None: _ = CircBox(circ) +def test_pickle_bit() -> None: + # https://github.com/CQCL/tket/issues/1293 + for b in [Bit(1), Bit("z", 0), Bit("z", (2, 0, 3))]: + assert b == pickle.loads(pickle.dumps(b)) + + if __name__ == "__main__": test_circuit_gen() test_symbolic_ops() diff --git a/pytket/tests/classical_test.py b/pytket/tests/classical_test.py index 9a6c24cf2f..1c5cd4c81a 100644 --- a/pytket/tests/classical_test.py +++ b/pytket/tests/classical_test.py @@ -653,7 +653,7 @@ def test_wasmfilehandler_multivalue_clang() -> None: == """Functions in wasm file with the uid 6f821422038eec251d2f4e6bf2b9a5717b18b5c96a8a8e01fb49f080d9610f6e: function '__wasm_call_ctors' with 0 i32 parameter(s) and 0 i32 return value(s) function 'init' with 0 i32 parameter(s) and 0 i32 return value(s) -function 'divmod' with 2 i32 parameter(s) and 2 i32 return value(s) +unsupported function with invalid parameter or result type: 'divmod' """ ) @@ -1032,12 +1032,18 @@ def compare_commands_box( if print_com: print(c1, c2) if c1.op.type == OpType.ClassicalExpBox: + assert isinstance(c1.op, ClassicalExpBox) + assert isinstance(c2.op, ClassicalExpBox) commands_equal &= c1.op.content_equality(c2.op) commands_equal &= c1.args == c2.args elif c1.op.type == OpType.Conditional: + assert isinstance(c1.op, Conditional) + assert isinstance(c2.op, Conditional) commands_equal &= c1.op.value == c2.op.value commands_equal &= c1.op.width == c2.op.width if c1.op.op.type == OpType.ClassicalExpBox: + assert isinstance(c1.op.op, ClassicalExpBox) + assert isinstance(c2.op.op, ClassicalExpBox) commands_equal &= c1.op.op.content_equality(c2.op.op) else: commands_equal &= c1.op == c2.op diff --git a/pytket/tests/passes_serialisation_test.py b/pytket/tests/passes_serialisation_test.py index 47bc2f1931..07b0cab816 100644 --- a/pytket/tests/passes_serialisation_test.py +++ b/pytket/tests/passes_serialisation_test.py @@ -300,7 +300,6 @@ def nonparam_predicate_dict(name: str) -> Dict[str, Any]: "RebaseTket", "RebaseUFR", "RemoveRedundancies", - "SynthesiseHQS", "SynthesiseTK", "SynthesiseTket", "SynthesiseOQC", diff --git a/pytket/tests/predicates_test.py b/pytket/tests/predicates_test.py index e9c15572d5..2afc1d625b 100644 --- a/pytket/tests/predicates_test.py +++ b/pytket/tests/predicates_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import sympy - +import pytest from pytket import logging from pytket.circuit import ( Circuit, @@ -33,7 +33,6 @@ SequencePass, RemoveRedundancies, SynthesiseTket, - SynthesiseHQS, SynthesiseUMD, RepeatUntilSatisfiedPass, CommuteThroughMultis, @@ -70,9 +69,11 @@ FlattenRelabelRegistersPass, RoundAngles, PeepholeOptimise2Q, - SynthesiseHQS, CliffordResynthesis, + CliffordPushThroughMeasures, CliffordSimp, + SynthesiseOQC, + ZXGraphlikeOptimisation, ) from pytket.predicates import ( GateSetPredicate, @@ -132,7 +133,7 @@ def test_compilation_unit_generation() -> None: def test_compilerpass_seq() -> None: - passlist = [SynthesiseTket(), SynthesiseOQC(), SynthesiseUMD(), SynthesiseHQS()] + passlist = [SynthesiseTket(), SynthesiseOQC(), SynthesiseUMD()] seq = SequencePass(passlist) circ = Circuit(2) circ.X(0).Z(1) @@ -142,6 +143,16 @@ def test_compilerpass_seq() -> None: assert seq.apply(cu2) +def test_compilerpass_seq_nonstrict() -> None: + passlist = [RebaseTket(), ZXGraphlikeOptimisation()] + with pytest.raises(RuntimeError): + _ = SequencePass(passlist) + seq = SequencePass(passlist, strict=False) + circ = Circuit(2) + seq.apply(circ) + assert np.allclose(circ.get_unitary(), np.eye(4, 4, dtype=complex)) + + def test_rebase_pass_generation() -> None: cx = Circuit(2) cx.CX(0, 1) @@ -648,7 +659,7 @@ def after_apply(self, cu: CompilationUnit, config: Dict[str, Any]) -> None: def compile(circ: Circuit, handler: CallbackHandler) -> bool: p = SequencePass([CommuteThroughMultis(), RemoveRedundancies()]) - return p.apply(circ, handler.before_apply, handler.after_apply) # type: ignore + return p.apply(circ, handler.before_apply, handler.after_apply) circ = Circuit(5) circ.CX(0, 1) @@ -971,15 +982,6 @@ def test_selectively_decompose_boxes() -> None: assert cmds[2].op.type == OpType.CircBox -def test_SynthesiseHQS_deprecation(capfd: Any) -> None: - logging.set_level(logging.level.warn) - p = SynthesiseHQS() - out = capfd.readouterr().out - assert "[warn]" in out - assert "deprecated" in out - logging.set_level(logging.level.err) - - def test_clifford_resynthesis() -> None: circ = ( Circuit(5) @@ -1027,6 +1029,33 @@ def T(c: Circuit) -> Circuit: assert circ.implicit_qubit_permutation() == {Qubit(0): Qubit(1), Qubit(1): Qubit(0)} +def test_clifford_push_through_measures() -> None: + c_x: Circuit = Circuit(2, 2).X(0).measure_all() + CliffordPushThroughMeasures().apply(c_x) + assert c_x == Circuit(2, 2).X(0).measure_all() + c_cx_x: Circuit = Circuit(2, 2).X(0).CX(0, 1).X(0).measure_all() + CliffordPushThroughMeasures().apply(c_cx_x) + assert c_cx_x.n_1qb_gates() == 0 + assert c_cx_x.n_2qb_gates() == 0 + coms = c_cx_x.get_commands() + assert len(coms) == 8 + assert coms[2].op.type == OpType.SetBits + assert coms[3].op.type == OpType.ExplicitModifier + assert coms[4].op.type == OpType.ExplicitModifier + assert coms[5].op.type == OpType.ExplicitModifier + assert coms[6].op.type == OpType.ExplicitModifier + assert coms[7].op.type == OpType.CopyBits + + +def test_SynthesiseOQC_deprecation(capfd: Any) -> None: + logging.set_level(logging.level.warn) + p = SynthesiseOQC() + out = capfd.readouterr().out + assert "[warn]" in out + assert "deprecated" in out + logging.set_level(logging.level.err) + + if __name__ == "__main__": test_predicate_generation() test_compilation_unit_generation() @@ -1046,3 +1075,4 @@ def T(c: Circuit) -> Circuit: test_flatten_relabel_pass() test_rebase_custom_tk2() test_selectively_decompose_boxes() + test_clifford_push_through_measures() diff --git a/pytket/tests/qasm_test.py b/pytket/tests/qasm_test.py index 629efaadfb..93e55912d1 100644 --- a/pytket/tests/qasm_test.py +++ b/pytket/tests/qasm_test.py @@ -152,14 +152,6 @@ def test_qasm_str_roundtrip() -> None: assert c == c2 -def test_qasm_str_roundtrip_oqc() -> None: - with open(curr_file_path / "qasm_test_files/test15.qasm", "r") as f: - c = circuit_from_qasm_str(f.read()) - qasm_str = circuit_to_qasm_str(c, "oqclib1") - c2 = circuit_from_qasm_str(qasm_str) - assert c == c2 - - def test_readout() -> None: fname = str(curr_file_path / "qasm_test_files/testout2.qasm") circ = Circuit(3) @@ -460,7 +452,7 @@ def test_opaque() -> None: ) with pytest.raises(QASMUnsupportedError) as e: circuit_to_qasm_str(c) - assert "CustomGate myopaq has empty definition" in str(e.value) + assert "Empty CustomGates and opaque gates are not supported" in str(e.value) def test_alternate_encoding() -> None: @@ -888,6 +880,58 @@ def test_register_name_check() -> None: assert err_msg in str(e2.value) +def test_conditional_custom() -> None: + # https://github.com/CQCL/tket/issues/1299 + qasm0 = """ + OPENQASM 2.0; + include "qelib1.inc"; + + gate cx_o0 q0,q1 { x q0; cx q0,q1; x q0; } + + qreg q[2]; + creg c[1]; + + if(c==1) cx_o0 q[1],q[0]; + measure q[0] -> c[0]; + """ + + circ = circuit_from_qasm_str(qasm0) + qasm1 = circuit_to_qasm_str(circ) + assert ( + qasm1 + == """OPENQASM 2.0; +include "qelib1.inc"; + +qreg q[2]; +creg c[1]; +if(c==1) x q[1]; +if(c==1) cx q[1],q[0]; +if(c==1) x q[1]; +measure q[0] -> c[0]; +""" + ) + + +def test_nonstandard_gates() -> None: + # https://github.com/CQCL/tket/issues/1302 + circ = Circuit(2) + circ.CS(0, 1) + circ.ECR(0, 1) + circ.CSdg(0, 1) + qasm = circuit_to_qasm_str(circ) + assert "gate cs" in qasm + assert "gate ecr" in qasm + assert "gate csdg" in qasm + + +def test_conditional_nonstandard_gates() -> None: + # https://github.com/CQCL/tket/issues/1301 + circ = Circuit(2, 1) + circ.ZZMax(0, 1, condition=Bit(0)) + qasm = circuit_to_qasm_str(circ) + assert "if(c==1) zzmax q[0],q[1];" in qasm + + if __name__ == "__main__": test_qasm_correct() test_qasm_qubit() @@ -896,7 +940,6 @@ def test_register_name_check() -> None: test_qasm_measure() test_qasm_roundtrip() test_qasm_str_roundtrip() - test_qasm_str_roundtrip_oqc() test_readout() test_symbolic_write() test_custom_gate() diff --git a/pytket/tests/qasm_test_files/test15.qasm b/pytket/tests/qasm_test_files/test15.qasm deleted file mode 100644 index 6917585171..0000000000 --- a/pytket/tests/qasm_test_files/test15.qasm +++ /dev/null @@ -1,14 +0,0 @@ -OPENQASM 2.0; -include "oqclib1.inc"; -//some comments -qreg q[4]; -rz(1.5*pi) q[3]; -sx q[3]; -rz(0.5*pi) q[3]; -ecr q[0],q[3]; -rz(1.5*pi) q[3]; -sx q[3]; -ecr q[0] ,q[1]; -barrier q[0],q[3],q[2]; -sx q[2]; -ecr q[0],q[1]; \ No newline at end of file diff --git a/pytket/tests/transform_test.py b/pytket/tests/transform_test.py index f2a8b98b59..850b8fadd3 100644 --- a/pytket/tests/transform_test.py +++ b/pytket/tests/transform_test.py @@ -1474,6 +1474,23 @@ def test_selectively_decompose_boxes() -> None: assert cmds[2].op.type == OpType.CircBox +def test_clifford_push() -> None: + c_x: Circuit = Circuit(2, 2).X(0).measure_all() + assert not Transform.PushCliffordsThroughMeasures().apply(c_x) + c_cx_x: Circuit = Circuit(2, 2).X(0).CX(0, 1).X(0).measure_all() + assert Transform.PushCliffordsThroughMeasures().apply(c_cx_x) + assert c_cx_x.n_1qb_gates() == 0 + assert c_cx_x.n_2qb_gates() == 0 + coms = c_cx_x.get_commands() + assert len(coms) == 8 + assert coms[2].op.type == OpType.SetBits + assert coms[3].op.type == OpType.ExplicitModifier + assert coms[4].op.type == OpType.ExplicitModifier + assert coms[5].op.type == OpType.ExplicitModifier + assert coms[6].op.type == OpType.ExplicitModifier + assert coms[7].op.type == OpType.CopyBits + + if __name__ == "__main__": test_remove_redundancies() test_reduce_singles() @@ -1505,3 +1522,4 @@ def test_selectively_decompose_boxes() -> None: test_auto_rebase_with_swap_zzphase() test_auto_rebase_with_swap_tk2() test_selectively_decompose_boxes() + test_clifford_push() diff --git a/recipes/pybind11/conanfile.py b/recipes/pybind11/conanfile.py index 63a4475cd7..249903873c 100644 --- a/recipes/pybind11/conanfile.py +++ b/recipes/pybind11/conanfile.py @@ -21,7 +21,7 @@ class PyBind11Conan(ConanFile): name = "pybind11" - version = "2.11.1" + version = "2.12.0" description = "Seamless operability between C++11 and Python" topics = "pybind11", "python", "binding" homepage = "https://github.com/pybind/pybind11" diff --git a/recipes/pybind11_json/all/conanfile.py b/recipes/pybind11_json/all/conanfile.py index cb5a418e59..703c099fbf 100644 --- a/recipes/pybind11_json/all/conanfile.py +++ b/recipes/pybind11_json/all/conanfile.py @@ -29,7 +29,7 @@ def package_id(self): def requirements(self): self.requires("nlohmann_json/3.11.3") - self.requires("pybind11/2.11.1") + self.requires("pybind11/2.12.0") def source(self): get( diff --git a/schemas/compiler_pass_v1.json b/schemas/compiler_pass_v1.json index b7511d54d2..404e19f954 100644 --- a/schemas/compiler_pass_v1.json +++ b/schemas/compiler_pass_v1.json @@ -124,7 +124,6 @@ "RebaseTket", "RebaseUFR", "RemoveRedundancies", - "SynthesiseHQS", "SynthesiseTK", "SynthesiseTket", "SynthesiseOQC", @@ -832,7 +831,6 @@ "RebaseTket", "RebaseUFR", "RemoveRedundancies", - "SynthesiseHQS", "SynthesiseTK", "SynthesiseTket", "SynthesiseOQC", diff --git a/tket/CMakeLists.txt b/tket/CMakeLists.txt index 5d534e4316..0185f4ece6 100644 --- a/tket/CMakeLists.txt +++ b/tket/CMakeLists.txt @@ -225,7 +225,6 @@ target_sources(tket src/ZX/MBQCRewrites.cpp src/ZX/ZXRWSequences.cpp src/Converters/ChoiMixTableauConverters.cpp - src/Converters/PauliGadget.cpp src/Converters/PauliGraphConverters.cpp src/Converters/Gauss.cpp src/Converters/PhasePoly.cpp @@ -379,7 +378,6 @@ target_sources(tket include/tket/ZX/ZXGenerator.hpp include/tket/Converters/Converters.hpp include/tket/Converters/Gauss.hpp - include/tket/Converters/PauliGadget.hpp include/tket/Converters/PhasePoly.hpp include/tket/Converters/UnitaryTableauBox.hpp include/tket/Placement/NeighbourPlacements.hpp diff --git a/tket/README.md b/tket/README.md index 8352e5e7cf..2bcd81d034 100644 --- a/tket/README.md +++ b/tket/README.md @@ -38,7 +38,7 @@ wget http://mirrors.ctan.org/graphics/pgf/contrib/quantikz/tikzlibraryquantikz.c To build tket and its dependencies and run all unit and property tests, use: ```shell -conan build tket --build=missing -o "boost/*":header_only=True -o with_all_tests=True +conan build tket --user=tket --channel=stable --build=missing -o "boost/*":header_only=True -o with_all_tests=True ``` or `with_test`/`with_proptest` to only build and run the unit tests or proptests, respectively. @@ -51,8 +51,8 @@ build directories `tket/build/Release` and/or `tket/build/Debug`. They can be run from those directories, e.g.: ```shell cd tket/build/Release -./test/tket-test -./proptest/tket-proptest +./test/test-tket +./proptest/proptest-tket ``` The tests with a running time >=1 second (on a regular modern laptop) are marked diff --git a/tket/conanfile.py b/tket/conanfile.py index df52d0f029..cb0f27dcba 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.101" + version = "1.2.117" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" @@ -121,7 +121,7 @@ def requirements(self): self.requires("tktokenswap/0.3.7@tket/stable") self.requires("tkwsm/0.3.7@tket/stable") if self.build_test(): - self.test_requires("catch2/3.5.2") + self.test_requires("catch2/3.5.4") if self.build_proptest(): self.test_requires("rapidcheck/cci.20230815") diff --git a/tket/include/tket/Circuit/Boxes.hpp b/tket/include/tket/Circuit/Boxes.hpp index b73ed436e4..8e88bcf14a 100644 --- a/tket/include/tket/Circuit/Boxes.hpp +++ b/tket/include/tket/Circuit/Boxes.hpp @@ -172,6 +172,20 @@ class CircBox : public Box { static nlohmann::json to_json(const Op_ptr &op); + /** + * Get the name of the inner circuit. + * + * @return name + */ + std::optional get_circuit_name() const; + + /** + * Set the name of the inner circuit + * + * @param _name name string + */ + void set_circuit_name(const std::string _name); + protected: void generate_circuit() const override {} // Already set by constructor }; diff --git a/tket/include/tket/Circuit/CircPool.hpp b/tket/include/tket/Circuit/CircPool.hpp index 55e3ea523e..461e909f0f 100644 --- a/tket/include/tket/Circuit/CircPool.hpp +++ b/tket/include/tket/Circuit/CircPool.hpp @@ -109,9 +109,6 @@ const Circuit &SWAP_using_CX_0(); /** Equivalent to SWAP, using three CX, outer CX have control on qubit 1 */ const Circuit &SWAP_using_CX_1(); -/** A two-qubit circuit with an Rz(1) on each qubit */ -const Circuit &two_Rz1(); - /** X[1]; CX[0,1] */ const Circuit &X1_CX(); @@ -444,6 +441,12 @@ Circuit PhasedISWAP_using_TK2(const Expr &p, const Expr &t); /** Equivalent to PhasedISWAP, using CX, U3 and Rz gates */ Circuit PhasedISWAP_using_CX(const Expr &p, const Expr &t); +/** Equivalent to AAMS, using a TK2 and Rz gates */ +Circuit AAMS_using_TK2(const Expr &theta, const Expr &phi0, const Expr &phi1); + +/** Equivalent to AAMS, using CX, Rz and U3 gates */ +Circuit AAMS_using_CX(const Expr &theta, const Expr &phi0, const Expr &phi1); + /** Unwrap NPhasedX, into number_of_qubits PhasedX gates */ Circuit NPhasedX_using_PhasedX( unsigned int number_of_qubits, const Expr &alpha, const Expr &beta); diff --git a/tket/include/tket/Circuit/CircUtils.hpp b/tket/include/tket/Circuit/CircUtils.hpp index ae04b26ada..281ce94eb7 100644 --- a/tket/include/tket/Circuit/CircUtils.hpp +++ b/tket/include/tket/Circuit/CircUtils.hpp @@ -111,13 +111,13 @@ std::pair decompose_2cx_DV(const Eigen::Matrix4cd& U); * Construct a phase gadget * * @param n_qubits number of qubits - * @param t phase parameter + * @param angle phase parameter * @param cx_config CX configuration * * @return phase gadget implementation wrapped in a ConjugationBox */ Circuit phase_gadget( - unsigned n_qubits, const Expr& t, + unsigned n_qubits, const Expr& angle, CXConfigType cx_config = CXConfigType::Snake); /** @@ -127,13 +127,29 @@ Circuit phase_gadget( * \f$ e^{-\frac12 i \pi t \sigma_0 \otimes \sigma_1 \otimes \cdots} \f$ * where \f$ \sigma_i \in \{I,X,Y,Z\} \f$ are the Pauli operators. * - * @param paulis Pauli operators - * @param t angle in half-turns + * @param pauli Pauli operators; coefficient gives rotation angle in half-turns * @param cx_config CX configuration * @return Pauli gadget implementation wrapped in a ConjugationBox */ Circuit pauli_gadget( - const std::vector& paulis, const Expr& t, + SpSymPauliTensor pauli, CXConfigType cx_config = CXConfigType::Snake); + +/** + * Construct a circuit realising a pair of Pauli gadgets with the fewest + * two-qubit gates. + * + * The returned circuit implements the unitary e^{-i pi angle1 paulis1 / 2} + * e^{-i pi angle0 paulis0 / 2}, i.e. a gadget of angle0 about paulis0 followed + * by a gadget of angle1 about paulis1. + * + * @param paulis0 Pauli operators for first gadget; coefficient gives rotation + * angle in half-turns + * @param paulis1 Pauli operators for second gadget; coefficient gives rotation + * angle in half-turns + * @param cx_config CX configuration + */ +Circuit pauli_gadget_pair( + SpSymPauliTensor paulis0, SpSymPauliTensor paulis1, CXConfigType cx_config = CXConfigType::Snake); /** diff --git a/tket/include/tket/Circuit/Circuit.hpp b/tket/include/tket/Circuit/Circuit.hpp index a7e34ea391..04cbb56cc0 100644 --- a/tket/include/tket/Circuit/Circuit.hpp +++ b/tket/include/tket/Circuit/Circuit.hpp @@ -1821,7 +1821,6 @@ Vertex Circuit::add_op( add_wasm_register(count_wasm_sig); - Vertex new_v = add_vertex(op, opgroup); unit_set_t write_arg_set; EdgeVec preds; for (unsigned i = 0; i < args.size(); ++i) { @@ -1837,6 +1836,7 @@ Vertex Circuit::add_op( preds.push_back(pred_out_e); } + Vertex new_v = add_vertex(op, opgroup); rewire(new_v, preds, sig); return new_v; } diff --git a/tket/include/tket/Circuit/PauliExpBoxes.hpp b/tket/include/tket/Circuit/PauliExpBoxes.hpp index 325828f577..151bb12849 100644 --- a/tket/include/tket/Circuit/PauliExpBoxes.hpp +++ b/tket/include/tket/Circuit/PauliExpBoxes.hpp @@ -275,4 +275,46 @@ class TermSequenceBox : public Box { CXConfigType cx_configuration_; }; +/** + * Constructs a PauliExpBox for a single pauli gadget and appends it to a + * circuit. + * + * @param circ The circuit to append the box to + * @param pauli The pauli operator of the gadget; coefficient gives the rotation + * angle in half-turns + * @param cx_config The CX configuration to be used during synthesis + */ +void append_single_pauli_gadget_as_pauli_exp_box( + Circuit &circ, const SpSymPauliTensor &pauli, CXConfigType cx_config); + +/** + * Constructs a PauliExpPairBox for a pair of pauli gadgets and appends it to a + * circuit. The pauli gadgets may or may not commute, so the ordering matters. + * + * @param circ The circuit to append the box to + * @param pauli0 The pauli operator of the first gadget; coefficient gives the + * rotation angle in half-turns + * @param pauli1 The pauli operator of the second gadget; coefficient gives the + * rotation angle in half-turns + * @param cx_config The CX configuration to be used during synthesis + */ +void append_pauli_gadget_pair_as_box( + Circuit &circ, const SpSymPauliTensor &pauli0, + const SpSymPauliTensor &pauli1, CXConfigType cx_config); + +/** + * Constructs a PauliExpCommutingSetBox for a set of mutually commuting pauli + * gadgets and appends it to a circuit. As the pauli gadgets all commute, the + * ordering does not matter semantically, but may yield different synthesised + * circuits. + * + * @param circ The circuit to append the box to + * @param gadgets Description of the pauli gadgets; coefficients give the + * rotation angles in half-turns + * @param cx_config The CX configuration to be used during synthesis + */ +void append_commuting_pauli_gadget_set_as_box( + Circuit &circ, const std::list &gadgets, + CXConfigType cx_config); + } // namespace tket diff --git a/tket/include/tket/Clifford/ChoiMixTableau.hpp b/tket/include/tket/Clifford/ChoiMixTableau.hpp index ea2e0bfc51..8ecb7c8808 100644 --- a/tket/include/tket/Clifford/ChoiMixTableau.hpp +++ b/tket/include/tket/Clifford/ChoiMixTableau.hpp @@ -45,7 +45,8 @@ class ChoiMixTableau { * When mapped to a sparse readable representation, independent * SpPauliStabiliser objects are used for each segment, so we no longer expect * their individual phases to be +-1, instead only requiring this on their - * product. + * product. get_row() will automatically transpose the input segment term so + * it is presented as RxS s.t. SCR = C. * * Columns of the tableau are indexed by pair of Qubit id and a tag to * distinguish input vs output. Rows are not maintained in any particular @@ -93,6 +94,7 @@ class ChoiMixTableau { * Construct a tableau directly from its rows. * Each row is represented as a product of SpPauliStabilisers where the first * is over the input qubits and the second is over the outputs. + * A row RxS is a pair s.t. SCR = C */ explicit ChoiMixTableau(const std::list& rows); /** @@ -122,13 +124,23 @@ class ChoiMixTableau { * Get the number of boundaries representing outputs from the process. */ unsigned get_n_outputs() const; + /** + * Get all qubit names present in the input segment. + */ + qubit_vector_t input_qubits() const; + /** + * Get all qubit names present in the output segment. + */ + qubit_vector_t output_qubits() const; /** - * Read off a row as a Pauli string + * Read off a row as a Pauli string. + * Returns a pair of Pauli strings RxS such that SCR = C */ row_tensor_t get_row(unsigned i) const; /** - * Combine rows into a single row + * Combine rows into a single row. + * Returns a pair of Pauli strings RxS such that SCR = C */ row_tensor_t get_row_product(const std::vector& rows) const; @@ -142,7 +154,10 @@ class ChoiMixTableau { * outputs. */ void apply_S(const Qubit& qb, TableauSegment seg = TableauSegment::Output); + void apply_Z(const Qubit& qb, TableauSegment seg = TableauSegment::Output); void apply_V(const Qubit& qb, TableauSegment seg = TableauSegment::Output); + void apply_X(const Qubit& qb, TableauSegment seg = TableauSegment::Output); + void apply_H(const Qubit& qb, TableauSegment seg = TableauSegment::Output); void apply_CX( const Qubit& control, const Qubit& target, TableauSegment seg = TableauSegment::Output); diff --git a/tket/include/tket/Clifford/SymplecticTableau.hpp b/tket/include/tket/Clifford/SymplecticTableau.hpp index 961bbbef06..b353e3b5dd 100644 --- a/tket/include/tket/Clifford/SymplecticTableau.hpp +++ b/tket/include/tket/Clifford/SymplecticTableau.hpp @@ -20,12 +20,6 @@ namespace tket { -// Forward declare friend classes for converters -class ChoiMixTableau; -class UnitaryTableau; -class UnitaryRevTableau; -class Circuit; - /** * Boolean encoding of Pauli * = ==> I @@ -136,10 +130,13 @@ class SymplecticTableau { void row_mult(unsigned ra, unsigned rw, Complex coeff = 1.); /** - * Applies an S/V/CX gate to the given qubit(s) + * Applies a chosen gate to the given qubit(s) */ void apply_S(unsigned qb); + void apply_Z(unsigned qb); void apply_V(unsigned qb); + void apply_X(unsigned qb); + void apply_H(unsigned qb); void apply_CX(unsigned qc, unsigned qt); void apply_gate(OpType type, const std::vector &qbs); @@ -173,29 +170,19 @@ class SymplecticTableau { */ void gaussian_form(); - private: - /** - * Number of rows - */ - unsigned n_rows_; - - /** - * Number of qubits in each row - */ - unsigned n_qubits_; - /** * Tableau contents */ - MatrixXb xmat_; - MatrixXb zmat_; - VectorXb phase_; + MatrixXb xmat; + MatrixXb zmat; + VectorXb phase; /** * Complex conjugate of the state by conjugating rows */ SymplecticTableau conjugate() const; + private: /** * Helper methods for manipulating the tableau when applying gates */ @@ -206,18 +193,6 @@ class SymplecticTableau { void col_mult( const MatrixXb::ColXpr &a, const MatrixXb::ColXpr &b, bool flip, MatrixXb::ColXpr &w, VectorXb &pw); - - friend class UnitaryTableau; - friend class ChoiMixTableau; - friend Circuit unitary_tableau_to_circuit(const UnitaryTableau &tab); - friend std::pair cm_tableau_to_circuit( - const ChoiMixTableau &tab); - friend std::ostream &operator<<(std::ostream &os, const UnitaryTableau &tab); - friend std::ostream &operator<<( - std::ostream &os, const UnitaryRevTableau &tab); - - friend void to_json(nlohmann::json &j, const SymplecticTableau &tab); - friend void from_json(const nlohmann::json &j, SymplecticTableau &tab); }; JSON_DECL(SymplecticTableau) diff --git a/tket/include/tket/Clifford/UnitaryTableau.hpp b/tket/include/tket/Clifford/UnitaryTableau.hpp index 0c3b780a5c..1740eed478 100644 --- a/tket/include/tket/Clifford/UnitaryTableau.hpp +++ b/tket/include/tket/Clifford/UnitaryTableau.hpp @@ -101,8 +101,14 @@ class UnitaryTableau { */ void apply_S_at_front(const Qubit& qb); void apply_S_at_end(const Qubit& qb); + void apply_Z_at_front(const Qubit& qb); + void apply_Z_at_end(const Qubit& qb); void apply_V_at_front(const Qubit& qb); void apply_V_at_end(const Qubit& qb); + void apply_X_at_front(const Qubit& qb); + void apply_X_at_end(const Qubit& qb); + void apply_H_at_front(const Qubit& qb); + void apply_H_at_end(const Qubit& qb); void apply_CX_at_front(const Qubit& control, const Qubit& target); void apply_CX_at_end(const Qubit& control, const Qubit& target); void apply_gate_at_front(OpType type, const qubit_vector_t& qbs); @@ -244,8 +250,14 @@ class UnitaryRevTableau { */ void apply_S_at_front(const Qubit& qb); void apply_S_at_end(const Qubit& qb); + void apply_Z_at_front(const Qubit& qb); + void apply_Z_at_end(const Qubit& qb); void apply_V_at_front(const Qubit& qb); void apply_V_at_end(const Qubit& qb); + void apply_X_at_front(const Qubit& qb); + void apply_X_at_end(const Qubit& qb); + void apply_H_at_front(const Qubit& qb); + void apply_H_at_end(const Qubit& qb); void apply_CX_at_front(const Qubit& control, const Qubit& target); void apply_CX_at_end(const Qubit& control, const Qubit& target); void apply_gate_at_front(OpType type, const qubit_vector_t& qbs); diff --git a/tket/include/tket/Converters/Converters.hpp b/tket/include/tket/Converters/Converters.hpp index ff141a2432..a3de8c9146 100644 --- a/tket/include/tket/Converters/Converters.hpp +++ b/tket/include/tket/Converters/Converters.hpp @@ -47,13 +47,88 @@ ChoiMixTableau circuit_to_cm_tableau(const Circuit &circ); /** * Constructs a circuit producing the same effect as a ChoiMixTableau. - * Uses a naive synthesis method until we develop a good heuristic. * Since Circuit does not support distinct qubit addresses for inputs and * outputs, also returns a map from the output qubit IDs in the tableau to their - * corresponding outputs in the circuit + * corresponding outputs in the circuit. + * + * The circuit produced will be the (possibly non-unitary) channel whose + * stabilisers are exactly those of the tableau and no more, using + * initialisations, post-selections, discards, resets, and collapses to ensure + * this. It will automatically reuse qubits so no more qubits will be needed + * than max(tab.get_n_inputs(), tab.get_n_outputs()). + * + * Example 1: + * ZXI -> () + * YYZ -> () + * This becomes a diagonalisation circuit followed by post-selections. + * + * Example 2: + * Z -> ZZ + * X -> IY + * Z -> -XX + * Combining the first and last rows reveals an initialisation is required for I + * -> YY. Since there are two output qubits, at least one of them does not + * already exist in the input fragment so we can freely add an extra qubit on + * the input side, initialise it and apply a unitary mapping IZ -> YY. + * + * Example 3: + * ZX -> IZ + * II -> ZI + * We require an initialised qubit for the final row, but both input and output + * spaces only have q[0] and q[1], of which both inputs need to be open for the + * first row. We can obtain an initialised qubit by resetting a qubit after + * reducing the first row to only a single qubit. */ -std::pair cm_tableau_to_circuit( - const ChoiMixTableau &circ); +std::pair cm_tableau_to_exact_circuit( + const ChoiMixTableau &tab, CXConfigType cx_config = CXConfigType::Snake); + +/** + * We define a unitary extension of a ChoiMixTableau to be a unitary circuit + * whose stabilizer group contain all the rows of the ChoiMixTableau and + * possibly more. This is useful when we are treating the ChoiMixTableau as a + * means to encode a diagonalisation problem, since we are generally looking for + * a unitary as we may wish to apply the inverse afterwards (e.g. conjugating + * some rotations to implement a set of Pauli gadgets). + * + * Not every ChoiMixTableau can be extended to a unitary by just adding rows, + * e.g. if it requires any initialisation or post-selections. In this case, the + * unitary circuit is extended with additional input qubits which are assumed to + * be zero-initialised, and additional output qubits which are assumed to be + * post-selected. The synthesis guarantees that, if we take the unitary, + * initialise all designated inputs, and post-select on all designated outputs, + * every row from the original tableau is a stabiliser for the remaining + * projector. When not enough additional qubit names are provided, an error is + * thrown. + * + * + * Example 1: + * ZXI -> () + * YYZ -> () + * Since, in exact synthesis, at least two post-selections would be required, we + * pick two names from post_names. This is then a diagonalisation circuit which + * maps each row to an arbitrary diagonal string over post_names. + * + * Example 2: + * Z -> ZZ + * X -> IY + * Z -> -XX + * Combining the first and last rows reveals an initialisation is required for I + * -> YY. We extend the inputs with a qubit from init_names. The initialisation + * can manifest as either altering the first row to ZZ -> ZZ or the last row to + * ZZ -> -XX. + * + * Example 3: + * ZX -> IZ + * II -> ZI + * We require an initialised qubit for the final row, but both input and output + * spaces only have q[0] and q[1], of which both inputs need to be open for the + * first row. Unlike exact synthesis, we cannot reuse qubits, so the returned + * circuit will be over 3 qubits, extending with a name from init_names. + */ +std::pair cm_tableau_to_unitary_extension_circuit( + const ChoiMixTableau &tab, const std::vector &init_names = {}, + const std::vector &post_names = {}, + CXConfigType cx_config = CXConfigType::Snake); PauliGraph circuit_to_pauli_graph(const Circuit &circ); diff --git a/tket/include/tket/Converters/PauliGadget.hpp b/tket/include/tket/Converters/PauliGadget.hpp deleted file mode 100644 index ef09cba1a7..0000000000 --- a/tket/include/tket/Converters/PauliGadget.hpp +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2019-2024 Cambridge Quantum Computing -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include - -#include "tket/Circuit/Circuit.hpp" -#include "tket/Utils/PauliTensor.hpp" - -namespace tket { - -class ImplicitPermutationNotAllowed : public std::logic_error { - public: - explicit ImplicitPermutationNotAllowed(const std::string& message) - : std::logic_error(message) {} -}; - -/** - * Append a Pauli gadget to the end of a given circuit. - * Automatically uses Snake CX configuration - * - * @param circ circuit to append to - * @param pauli Pauli operators and their respective qubits; coefficient gives - * rotation angle in half-turns - * @param cx_config which type of CX configuration to decompose into - */ -void append_single_pauli_gadget( - Circuit& circ, const SpSymPauliTensor& pauli, - CXConfigType cx_config = CXConfigType::Snake); - -/** - * Append a Pauli gadget to the end of a given circuit as a - * PauliExpBox. - * Automatically uses Snake CX configuration - * - * @param circ circuit to append to - * @param pauli Pauli operators and their respective qubits; coefficient gives - * rotation angle in half-turns - * @param cx_config which type of CX configuration to decompose into - */ -void append_single_pauli_gadget_as_pauli_exp_box( - Circuit& circ, const SpSymPauliTensor& pauli, CXConfigType cx_config); - -/** - * Append a pair of Pauli gadgets to the end of a given circuit. - * (shallow) Uses an adapted arrangement of CX that gives balanced trees - * over the matching qubits to improve depth. Better performance - * is not guaranteed as CXs may not align for cancellation and - * it can be harder to route. - * (!shallow) Uses the original method with naive arrangement of CXs. - * - * @param circ circuit to append to - * @param pauli0 first Pauli string; coefficient gives rotation angle in - * half-turns - * @param pauli1 second Pauli string; coefficient gives rotation angle in - * half-turns - * @param cx_config which type of CX configuration to decompose into - */ -void append_pauli_gadget_pair( - Circuit& circ, SpSymPauliTensor pauli0, SpSymPauliTensor pauli1, - CXConfigType cx_config = CXConfigType::Snake); - -void append_pauli_gadget_pair_as_box( - Circuit& circ, const SpSymPauliTensor& pauli0, - const SpSymPauliTensor& pauli1, CXConfigType cx_config); - -void append_commuting_pauli_gadget_set_as_box( - Circuit& circ, const std::list& gadgets, - CXConfigType cx_config); - -} // namespace tket diff --git a/tket/include/tket/Diagonalisation/Diagonalisation.hpp b/tket/include/tket/Diagonalisation/Diagonalisation.hpp index 3d43ccd260..93fabbe92c 100644 --- a/tket/include/tket/Diagonalisation/Diagonalisation.hpp +++ b/tket/include/tket/Diagonalisation/Diagonalisation.hpp @@ -60,13 +60,51 @@ void apply_conjugations( SpSymPauliTensor &qps, const Conjugations &conjugations); /** - * Given two qubits on which to conjugate a CX gate, try to conjugate with a - * XXPhase3 instead. If successful, undoes conjugations that must be undone and - * replaces it with XXPhase3 conjugation. Returns true if successful and false - * otherwise. + * Given a Pauli tensor P, produces a short Clifford circuit C which maps P to Z + * on a single qubit, i.e. Z_i C P = C. This can be viewed as the components + * required to synthesise a single Pauli gadget C^dag RZ(a)_i C = exp(-i pi a + * P/2) (up to global phase), or as a diagonalisation of a single Pauli string + * along with CXs to reduce it to a single qubit. Returns the circuit C and the + * qubit i where the Z ends up. */ -bool conjugate_with_xxphase3( - const Qubit &qb_a, const Qubit &qb_b, Conjugations &conjugations, - Circuit &cliff_circ); +std::pair reduce_pauli_to_z( + const SpPauliStabiliser &pauli, CXConfigType cx_config); + +/** + * Given a pair of (either commuting or anticommuting) Pauli tensors P0, P1, + * produces a short Clifford circuit C which maps P0 and P1 to strings which + * overlap on at most one qubit (which is also returned). If P0 and P1 + * anticommute, a mismatching qubit is always left. If they commute, contain at + * least one matching qubit and no mismatching qubits, then the final matching + * qubit is returned if allow_matching_final is true, otherwise P0 and P1 are + * reduced to non-overlapping strings. + */ +std::pair> reduce_overlap_of_paulis( + SpPauliStabiliser &pauli0, SpPauliStabiliser &pauli1, + CXConfigType cx_config, bool allow_matching_final = false); + +/** + * Given a pair of anticommuting Pauli tensors P0, P1, produces a short Clifford + * circuit C which maps P0 to Z and P1 to X on the same qubit, i.e. Z_i C P0 = C + * = X_i C P1. This can be viewed as the components required to synthesise a + * pair of noncommuting Pauli gadgets C^dag RX(b)_i RZ(a)_i C = exp(-i pi b + * P1/2) exp(-i pi a P0/2) (up to global phase). This is not strictly a + * diagonalisation because anticommuting strings cannot be simultaneously + * diagonalised. Returns the circuit C and the qubit i where the Z and X end up. + */ +std::pair reduce_anticommuting_paulis_to_z_x( + SpPauliStabiliser pauli0, SpPauliStabiliser pauli1, CXConfigType cx_config); + +/** + * Given a pair of commuting Pauli tensors P0, P1, produces a short Clifford + * circuit C which maps P0 and P1 to Z on different qubits, i.e. Z_i C P0 = C = + * Z_j C P1. This can be viewed as the components required to synthesise a pair + * of commuting Pauli gadgets C^dag RZ(b)_j RZ(a)_i C = exp(-i pi b P1/2) exp(-i + * pi a P0/2) (up to global phase), or as a mutual diagonalisation of two Pauli + * strings along with CXs to reduce them to independent, individual qubits. + * Returns the circuit C and the qubits i and j where the Zs end up. + */ +std::tuple reduce_commuting_paulis_to_zi_iz( + SpPauliStabiliser pauli0, SpPauliStabiliser pauli1, CXConfigType cx_config); } // namespace tket diff --git a/tket/include/tket/Gate/GateUnitaryMatrixImplementations.hpp b/tket/include/tket/Gate/GateUnitaryMatrixImplementations.hpp index 961b247062..e04e14998d 100644 --- a/tket/include/tket/Gate/GateUnitaryMatrixImplementations.hpp +++ b/tket/include/tket/Gate/GateUnitaryMatrixImplementations.hpp @@ -68,6 +68,10 @@ struct GateUnitaryMatrixImplementations { static Eigen::Matrix2cd Rz(double value); static Eigen::Matrix2cd U1(double value); + static Eigen::Matrix2cd GPI(double value); + static Eigen::Matrix2cd GPI2(double value); + static Eigen::Matrix4cd AAMS(double theta, double phi0, double phi1); + static Eigen::Matrix2cd U2(double phi, double lambda); static Eigen::Matrix2cd U3(double theta, double phi, double lambda); diff --git a/tket/include/tket/OpType/OpType.hpp b/tket/include/tket/OpType/OpType.hpp index f97002f265..6be6ed96d5 100644 --- a/tket/include/tket/OpType/OpType.hpp +++ b/tket/include/tket/OpType/OpType.hpp @@ -259,6 +259,33 @@ enum class OpType { */ U1, + /** + * \f$ \mathrm{GPI}(\phi) = \left[ \begin{array}{cc} + * 0 & e^{-i\pi\phi} \\ e^{i\pi\phi} & 0 + * \end{array} \right] \f$ + */ + GPI, + + /** + * \f$ \mathrm{GPI2}(\phi) = \frac{1}{\sqrt 2} \left[ \begin{array}{cc} + * 1 & -ie^{-i\pi\phi} \\ -ie^{i\pi\phi} & 1 + * \end{array} \right] \f$ + */ + GPI2, + + /** + * \f$ \mathrm{AAMS}(\theta, \phi_0, \phi_1) = \left[ \begin{array}{cccc} + * \cos\frac{\pi\theta}{2} & 0 & 0 & + * -ie^{-i\pi(\phi_0+\phi_1)}\sin\frac{\pi\theta}{2} \\ + * 0 & \cos\frac{\pi\theta}{2} & + * -ie^{i\pi(\phi_1-\phi_0)}\sin\frac{\pi\theta}{2} & 0 \\ + * 0 & -ie^{i\pi(\phi_0-\phi_1)}\sin\frac{\pi\theta}{2} & + * \cos\frac{\pi\theta}{2} & 0 \\ + * -ie^{i\pi(\phi_0+\phi_1)}\sin\frac{\pi\theta}{2} & 0 & 0 & + * \cos\frac{\pi\theta}{2} \end{array} \right] \f$ + */ + AAMS, + /** * \f$ \mathrm{TK1}(\alpha, \beta, \gamma) = \mathrm{Rz}(\alpha) * \mathrm{Rx}(\beta) \mathrm{Rz}(\gamma) \f$ diff --git a/tket/include/tket/Predicates/CompilerPass.hpp b/tket/include/tket/Predicates/CompilerPass.hpp index 0b53496938..26ac7def6c 100644 --- a/tket/include/tket/Predicates/CompilerPass.hpp +++ b/tket/include/tket/Predicates/CompilerPass.hpp @@ -156,7 +156,7 @@ class BasePass { void update_cache(const CompilationUnit& c_unit, SafetyMode safe_mode) const; static PassConditions match_passes(const PassPtr& lhs, const PassPtr& rhs); static PassConditions match_passes( - const PassConditions& lhs, const PassConditions& rhs); + const PassConditions& lhs, const PassConditions& rhs, bool strict = true); }; /* Basic Pass that all combinators can be used on */ @@ -192,7 +192,7 @@ class StandardPass : public BasePass { class SequencePass : public BasePass { public: SequencePass() {} - explicit SequencePass(const std::vector& ptvec); + explicit SequencePass(const std::vector& ptvec, bool strict = true); bool apply( CompilationUnit& c_unit, SafetyMode safe_mode = SafetyMode::Default, const PassCallback& before_apply = trivial_callback, diff --git a/tket/include/tket/Predicates/PassGenerators.hpp b/tket/include/tket/Predicates/PassGenerators.hpp index ddabfe9387..55c17bd55c 100644 --- a/tket/include/tket/Predicates/PassGenerators.hpp +++ b/tket/include/tket/Predicates/PassGenerators.hpp @@ -75,6 +75,15 @@ PassPtr gen_clifford_resynthesis_pass( std::nullopt, bool allow_swaps = true); +/** + * Pass that simplifies circuits by resynthesising Clifford subcircuits before + * end of circuit measurements as a mutual diagonalisation circuit and classical + * postprocessing. + * + * @return pass to resynthesise pre end of circuit measure Clifford subcircuits + */ +PassPtr gen_clifford_push_through_pass(); + /** * Pass to remove empty Quantum edges from a Circuit and then relabel * all Qubit to some new register defined by a passed label. diff --git a/tket/include/tket/Predicates/PassLibrary.hpp b/tket/include/tket/Predicates/PassLibrary.hpp index d1dfb00ec1..ff3f3392c3 100644 --- a/tket/include/tket/Predicates/PassLibrary.hpp +++ b/tket/include/tket/Predicates/PassLibrary.hpp @@ -20,7 +20,6 @@ namespace tket { const PassPtr &SynthesiseTK(); const PassPtr &SynthesiseTket(); -const PassPtr &SynthesiseHQS(); const PassPtr &SynthesiseOQC(); const PassPtr &SynthesiseUMD(); diff --git a/tket/include/tket/Transformations/BasicOptimisation.hpp b/tket/include/tket/Transformations/BasicOptimisation.hpp index 3d4cdef665..54349befea 100644 --- a/tket/include/tket/Transformations/BasicOptimisation.hpp +++ b/tket/include/tket/Transformations/BasicOptimisation.hpp @@ -38,11 +38,6 @@ Transform squash_1qb_to_tk1(); // Produces: Any gates Transform commute_through_multis(); -// commutes Rz gates through ZZMax, and combines adjacent ZZMax gates -// Expects: ZZMax, Rz, Rx -// Produces: ZZMax, Rz, Rx -Transform commute_and_combine_HQS2(); - // squashes each sequence of two-qubit instructions into their canonical 3-CX // form (Cartan/KAK decomposition) The optional CX gate fidelity cx_fidelity // is used to produce circuit decompositions that maximise expected circuit @@ -84,11 +79,6 @@ Transform two_qubit_squash( bool allow_swaps = true); Transform two_qubit_squash(bool allow_swaps); -// 1qb squashing into -Rz-Rx-Rz- or -Rx-Rz-Rx- form -// Expects: Rx, Rz, and any multi-qubit gates -// Produces: Rx, Rz, and any multi-qubit gates -Transform reduce_XZ_chains(); - /** * general u_squash by converting any chains of p, q gates (p, q in * {Rx,Ry,Rz}) to triples -p-q-p- or -q-p-q- diff --git a/tket/include/tket/Transformations/CliffordOptimisation.hpp b/tket/include/tket/Transformations/CliffordOptimisation.hpp index 42d90e0b83..c8e820adab 100644 --- a/tket/include/tket/Transformations/CliffordOptimisation.hpp +++ b/tket/include/tket/Transformations/CliffordOptimisation.hpp @@ -34,6 +34,16 @@ Transform copy_pi_through_CX(); Transform singleq_clifford_sweep(); +// In some cases, 2-qubit gates can be saved by: +// 1) representing end-of-circuit measurements as a collection of single Z +// operators 2) transforming each fo these single Z operators to a new Pauli +// operator by acting on them with an end-of-circuit Clifford subcircuit 3) +// finding a Clifford that mutually diagonalises these Pauli operators 4) +// replacing the end-of-circuit Clifford subcircuit with the mutual +// diagonalisation circuit and adding classical logic to map the diagonalised +// Pauli operators back to the desired single Z operators +Transform push_cliffords_through_measures(); + } // namespace Transforms } // namespace tket diff --git a/tket/include/tket/Transformations/OptimisationPass.hpp b/tket/include/tket/Transformations/OptimisationPass.hpp index 175c46355c..cdc1fa4124 100644 --- a/tket/include/tket/Transformations/OptimisationPass.hpp +++ b/tket/include/tket/Transformations/OptimisationPass.hpp @@ -87,11 +87,6 @@ Transform synthesise_tket(); // Produces: Rz, SX, ECR Transform synthesise_OQC(); -// converts a circuit into the HQS primitives (Rz, PhasedX, ZZMax) whilst -// optimising Expects: CX and any single-qubit gates Produces: ZZMax, PhasedX, -// Rz -Transform synthesise_HQS(); - // converts a circuit into the UMD primitives (Rz, PhasedX, XXPhase) whilst // optimising Expects: Any gate set Produces: XXPhase, PhasedX, Rz Transform synthesise_UMD(); diff --git a/tket/include/tket/Transformations/PhasedXFrontier.hpp b/tket/include/tket/Transformations/PhasedXFrontier.hpp index 2ecb6bf4bb..cf5ec4348f 100644 --- a/tket/include/tket/Transformations/PhasedXFrontier.hpp +++ b/tket/include/tket/Transformations/PhasedXFrontier.hpp @@ -15,6 +15,7 @@ #pragma once #include +#include #include #include "SingleQubitSquash.hpp" @@ -289,6 +290,9 @@ class PhasedXFrontier { // squasher to squash gates SingleQubitSquash squasher_; + + // Access class attributes for testing + friend class PhasedXFrontierTester; }; /** @@ -296,6 +300,35 @@ class PhasedXFrontier { */ bool all_nullopt(const OptVertexVec& vec); +/** + * @brief Testing class for the PhasedXFrontier class. + * + * Gives access to current interval start and end vertices. + */ +class PhasedXFrontierTester { + public: + PhasedXFrontierTester(PhasedXFrontier& frontier, Circuit& circ); + + /** + * @brief Get the start of the interval on qubit `i`. + * + * @param i The qubit index. + * @return Vertex The gate before the current interval on qubit `i`. + */ + Vertex get_interval_start(unsigned i); + /** + * @brief Get the end of the interval on qubit `i`. + * + * @param i The qubit index. + * @return Vertex The gate after the current interval on qubit `i`. + */ + Vertex get_interval_end(unsigned i); + + private: + PhasedXFrontier const& frontier_; + Circuit const& circ_; +}; + } // namespace Transforms } // namespace tket diff --git a/tket/include/tket/Utils/UnitID.hpp b/tket/include/tket/Utils/UnitID.hpp index d4ee55a44f..408aa5a715 100644 --- a/tket/include/tket/Utils/UnitID.hpp +++ b/tket/include/tket/Utils/UnitID.hpp @@ -50,6 +50,7 @@ const std::string &node_default_reg(); const std::string &c_debug_zero_prefix(); const std::string &c_debug_one_prefix(); const std::string &c_debug_default_name(); +const std::string &c_permutation_scratch_name(); /** Conversion invalid */ class InvalidUnitConversion : public std::logic_error { diff --git a/tket/proptest/src/proptest.cpp b/tket/proptest/src/proptest.cpp index 788aa259d0..02a2bbc7b7 100644 --- a/tket/proptest/src/proptest.cpp +++ b/tket/proptest/src/proptest.cpp @@ -30,7 +30,6 @@ using namespace tket; #define ALL_PASSES(DO) \ DO(SynthesiseTK) \ DO(SynthesiseTket) \ - DO(SynthesiseHQS) \ DO(SynthesiseUMD) \ DO(PeepholeOptimise2Q) \ DO(FullPeepholeOptimise) \ diff --git a/tket/src/Circuit/Boxes.cpp b/tket/src/Circuit/Boxes.cpp index 8a32dfa40c..fa54e2809f 100644 --- a/tket/src/Circuit/Boxes.cpp +++ b/tket/src/Circuit/Boxes.cpp @@ -128,6 +128,16 @@ bool CircBox::is_equal(const Op &op_other) const { return circ_->circuit_equality(*other.circ_, {}, false); } +std::optional CircBox::get_circuit_name() const { + TKET_ASSERT(circ_ != nullptr); + return circ_->get_name(); +} + +void CircBox::set_circuit_name(const std::string _name) { + TKET_ASSERT(circ_ != nullptr); + circ_->set_name(_name); +} + Unitary1qBox::Unitary1qBox(const Eigen::Matrix2cd &m) : Box(OpType::Unitary1qBox), m_(m) { if (!is_unitary(m)) { diff --git a/tket/src/Circuit/CircPool.cpp b/tket/src/Circuit/CircPool.cpp index c29d868633..1b9d84368f 100644 --- a/tket/src/Circuit/CircPool.cpp +++ b/tket/src/Circuit/CircPool.cpp @@ -268,17 +268,6 @@ const Circuit &SWAP_using_CX_1() { return *C; } -const Circuit &two_Rz1() { - static std::unique_ptr C = std::make_unique([]() { - Circuit c(2); - Op_ptr z = get_op_ptr(OpType::Rz, 1.); - c.add_op(z, {0}); - c.add_op(z, {1}); - return c; - }()); - return *C; -} - const Circuit &X1_CX() { static std::unique_ptr C = std::make_unique([]() { Circuit c(2); @@ -1206,6 +1195,28 @@ Circuit PhasedISWAP_using_CX(const Expr &p, const Expr &t) { return c; } +Circuit AAMS_using_TK2(const Expr &theta, const Expr &phi0, const Expr &phi1) { + Circuit c(2); + c.add_op(OpType::Rz, -phi0, {0}); + c.add_op(OpType::Rz, -phi1, {1}); + c.add_op(OpType::TK2, {theta, 0, 0}, {0, 1}); + c.add_op(OpType::Rz, phi0, {0}); + c.add_op(OpType::Rz, phi1, {1}); + return c; +} + +Circuit AAMS_using_CX(const Expr &theta, const Expr &phi0, const Expr &phi1) { + Circuit c(2); + c.add_op(OpType::Rz, -phi0, {0}); + c.add_op(OpType::Rz, -phi1, {1}); + c.add_op(OpType::CX, {0, 1}); + c.add_op(OpType::U3, {theta, -0.5, 0.5}, {0}); + c.add_op(OpType::CX, {0, 1}); + c.add_op(OpType::Rz, phi0, {0}); + c.add_op(OpType::Rz, phi1, {1}); + return c; +} + Circuit NPhasedX_using_PhasedX( unsigned int number_of_qubits, const Expr &alpha, const Expr &beta) { Circuit c(number_of_qubits); diff --git a/tket/src/Circuit/CircUtils.cpp b/tket/src/Circuit/CircUtils.cpp index 45c820b858..fe23674bc5 100644 --- a/tket/src/Circuit/CircUtils.cpp +++ b/tket/src/Circuit/CircUtils.cpp @@ -22,6 +22,7 @@ #include "tket/Circuit/CircPool.hpp" #include "tket/Circuit/Circuit.hpp" #include "tket/Circuit/ConjugationBox.hpp" +#include "tket/Diagonalisation/Diagonalisation.hpp" #include "tket/Gate/GatePtr.hpp" #include "tket/Gate/GateUnitaryMatrixImplementations.hpp" #include "tket/Gate/Rotation.hpp" @@ -269,61 +270,69 @@ std::pair decompose_2cx_DV(const Eigen::Matrix4cd &U) { } Circuit phase_gadget(unsigned n_qubits, const Expr &t, CXConfigType cx_config) { - // Handle n_qubits==0 as a special case, or the calculations below - // go badly wrong. - Circuit new_circ(n_qubits); - Circuit compute(n_qubits); - Circuit action(n_qubits); - Circuit uncompute(n_qubits); - if (n_qubits == 0) { - new_circ.add_phase(-t / 2); - return new_circ; + return pauli_gadget( + SpSymPauliTensor(DensePauliMap(n_qubits, Pauli::Z), t), cx_config); +} + +Circuit pauli_gadget(SpSymPauliTensor paulis, CXConfigType cx_config) { + Circuit circ; + for (const std::pair &qp : paulis.string) + circ.add_qubit(qp.first); + if (SpPauliString(paulis.string) == SpPauliString()) { + circ.add_phase(-paulis.coeff / 2); + return circ; + } + Circuit compute(circ); + Circuit action(circ); + qubit_vector_t qubits; + for (const std::pair &qp : paulis.string) { + switch (qp.second) { + case Pauli::I: + break; + case Pauli::X: + compute.add_op(OpType::H, {qp.first}); + qubits.push_back(qp.first); + break; + case Pauli::Y: + compute.add_op(OpType::V, {qp.first}); + qubits.push_back(qp.first); + break; + case Pauli::Z: + qubits.push_back(qp.first); + break; + } } + unsigned n_qubits = qubits.size(); switch (cx_config) { case CXConfigType::Snake: { for (unsigned i = n_qubits - 1; i != 0; --i) { unsigned j = i - 1; - compute.add_op(OpType::CX, {i, j}); - } - action.add_op(OpType::Rz, t, {0}); - for (unsigned i = 0; i != n_qubits - 1; ++i) { - unsigned j = i + 1; - uncompute.add_op(OpType::CX, {j, i}); + compute.add_op(OpType::CX, {qubits[i], qubits[j]}); } + action.add_op(OpType::Rz, paulis.coeff, {qubits[0]}); break; } case CXConfigType::Star: { for (unsigned i = n_qubits - 1; i != 0; --i) { - compute.add_op(OpType::CX, {i, 0}); - } - action.add_op(OpType::Rz, t, {0}); - for (unsigned i = 1; i != n_qubits; ++i) { - uncompute.add_op(OpType::CX, {i, 0}); + compute.add_op(OpType::CX, {qubits[i], qubits[0]}); } + action.add_op(OpType::Rz, paulis.coeff, {qubits[0]}); break; } case CXConfigType::Tree: { unsigned complete_layers = floor(log2(n_qubits)); unsigned dense_end = pow(2, complete_layers); for (unsigned i = 0; i < n_qubits - dense_end; i++) - compute.add_op( - OpType::CX, {dense_end + i, dense_end - 1 - i}); + compute.add_op( + OpType::CX, {qubits[dense_end + i], qubits[dense_end - 1 - i]}); for (unsigned step_size = 1; step_size < dense_end; step_size *= 2) { for (unsigned i = 0; i < dense_end; i += 2 * step_size) - compute.add_op(OpType::CX, {i + step_size, i}); - } - action.add_op(OpType::Rz, t, {0}); - for (unsigned step_size = dense_end / 2; step_size >= 1; step_size /= 2) { - for (unsigned i = 0; i < dense_end; i += 2 * step_size) - uncompute.add_op(OpType::CX, {i + step_size, i}); + compute.add_op(OpType::CX, {qubits[i + step_size], qubits[i]}); } - for (unsigned i = 0; i < n_qubits - dense_end; i++) - uncompute.add_op( - OpType::CX, {dense_end + i, dense_end - 1 - i}); + action.add_op(OpType::Rz, paulis.coeff, {qubits[0]}); break; } case CXConfigType::MultiQGate: { - std::vector> conjugations; int sign_correction = 1; for (int q = n_qubits - 1; q > 0; q -= 2) { if (q - 1 > 0) { @@ -331,89 +340,72 @@ Circuit phase_gadget(unsigned n_qubits, const Expr &t, CXConfigType cx_config) { // this is only equal to the CX decompositions above // up to phase, but phase differences are cancelled out by // its dagger XXPhase(-1/2) below. - compute.add_op(OpType::H, {i}); - compute.add_op(OpType::H, {j}); - compute.add_op(OpType::XXPhase3, 0.5, {i, j, 0}); + compute.add_op(OpType::H, {qubits[i]}); + compute.add_op(OpType::H, {qubits[j]}); + compute.add_op( + OpType::XXPhase3, 0.5, {qubits[i], qubits[j], qubits[0]}); sign_correction *= -1; - conjugations.push_back({i, j, 0}); } else { unsigned i = q; - compute.add_op(OpType::CX, {i, 0}); - conjugations.push_back({i, 0}); - } - } - action.add_op(OpType::Rz, sign_correction * t, {0}); - for (const auto &conj : conjugations) { - if (conj.size() == 2) { - uncompute.add_op(OpType::CX, conj); - } else { - TKET_ASSERT(conj.size() == 3); - uncompute.add_op(OpType::XXPhase3, -0.5, conj); - uncompute.add_op(OpType::H, {conj[0]}); - uncompute.add_op(OpType::H, {conj[1]}); + compute.add_op(OpType::CX, {qubits[i], qubits[0]}); } } + action.add_op( + OpType::Rz, sign_correction * paulis.coeff, {qubits[0]}); break; } } + // ConjugationBox components must be in the default register + compute.flatten_registers(); + action.flatten_registers(); ConjugationBox box( - std::make_shared(compute), std::make_shared(action), - std::make_shared(uncompute)); - new_circ.add_box(box, new_circ.all_qubits()); - return new_circ; + std::make_shared(compute), std::make_shared(action)); + circ.add_box(box, circ.all_qubits()); + return circ; } -Circuit pauli_gadget( - const std::vector &paulis, const Expr &t, CXConfigType cx_config) { - unsigned n = paulis.size(); - Circuit circ(n); - Circuit compute(n); - Circuit action(n); - Circuit uncompute(n); - std::vector qubits; - for (unsigned i = 0; i < n; i++) { - switch (paulis[i]) { - case Pauli::I: - break; - case Pauli::X: - compute.add_op(OpType::H, {i}); - qubits.push_back(i); - break; - case Pauli::Y: - compute.add_op(OpType::V, {i}); - qubits.push_back(i); - break; - case Pauli::Z: - qubits.push_back(i); - break; - } - } - if (qubits.empty()) { - circ.add_phase(-t / 2); +Circuit pauli_gadget_pair( + SpSymPauliTensor paulis0, SpSymPauliTensor paulis1, + CXConfigType cx_config) { + Circuit circ; + for (const std::pair &qp : paulis0.string) + circ.add_qubit(qp.first); + for (const std::pair &qp : paulis1.string) + circ.add_qubit(qp.first, false); + if (SpPauliString(paulis0.string) == SpPauliString{}) { + circ.append(pauli_gadget(paulis1, cx_config)); + circ.add_phase(-paulis0.coeff / 2); + return circ; + } else if (SpPauliString(paulis1.string) == SpPauliString{}) { + circ.append(pauli_gadget(paulis0, cx_config)); + circ.add_phase(-paulis1.coeff / 2); return circ; } - Vertex v = action.add_op(OpType::PhaseGadget, t, qubits); - Circuit cx_gadget = phase_gadget(action.n_in_edges(v), t, cx_config); - Subcircuit sub = {action.get_in_edges(v), action.get_all_out_edges(v), {v}}; - action.substitute(cx_gadget, sub, Circuit::VertexDeletion::Yes); - for (unsigned i = 0; i < n; i++) { - switch (paulis[i]) { - case Pauli::I: - break; - case Pauli::X: - uncompute.add_op(OpType::H, {i}); - break; - case Pauli::Y: - uncompute.add_op(OpType::Vdg, {i}); - break; - case Pauli::Z: - break; - } - } - ConjugationBox box( - std::make_shared(compute), std::make_shared(action), - std::make_shared(uncompute)); - circ.add_box(box, circ.all_qubits()); + paulis0.compress(); + paulis1.compress(); + Circuit u(circ); + Circuit v(circ); + + // Reduce the overlap down to at most 1 qubit, which may be matching or + // mismatching; allow both gadgets to build on that qubit + SpPauliStabiliser p0stab(paulis0.string); + SpPauliStabiliser p1stab(paulis1.string); + u.append(reduce_overlap_of_paulis(p0stab, p1stab, cx_config, true).first); + paulis0 = SpSymPauliTensor(p0stab) * SpSymPauliTensor({}, paulis0.coeff); + paulis1 = SpSymPauliTensor(p1stab) * SpSymPauliTensor({}, paulis1.coeff); + + /* + * Combine circuits to give final result + */ + v.append(pauli_gadget(paulis0)); + v.append(pauli_gadget(paulis1)); + // ConjugationBox components must be in the default register + qubit_vector_t all_qubits = u.all_qubits(); + u.flatten_registers(); + v.flatten_registers(); + ConjugationBox cjbox( + std::make_shared(u), std::make_shared(v)); + circ.add_box(cjbox, all_qubits); return circ; } @@ -509,6 +501,8 @@ Circuit with_TK2(Gate_ptr op) { return CircPool::CU1_using_TK2(params[0]); case OpType::XXPhase3: return CircPool::XXPhase3_using_TK2(params[0]); + case OpType::AAMS: + return CircPool::AAMS_using_TK2(params[0], params[1], params[2]); case OpType::CCX: case OpType::CSWAP: case OpType::BRIDGE: @@ -616,6 +610,8 @@ Circuit with_CX(Gate_ptr op) { return CircPool::PhasedISWAP_using_CX(params[0], params[1]); case OpType::NPhasedX: return CircPool::NPhasedX_using_PhasedX(n, params[0], params[1]); + case OpType::AAMS: + return CircPool::AAMS_using_CX(params[0], params[1], params[2]); default: throw CircuitInvalidity("Cannot decompose " + op->get_name()); } diff --git a/tket/src/Circuit/PauliExpBoxes.cpp b/tket/src/Circuit/PauliExpBoxes.cpp index 9fcd33a06f..c219426c34 100644 --- a/tket/src/Circuit/PauliExpBoxes.cpp +++ b/tket/src/Circuit/PauliExpBoxes.cpp @@ -18,7 +18,6 @@ #include "tket/Circuit/CircUtils.hpp" #include "tket/Circuit/ConjugationBox.hpp" -#include "tket/Converters/PauliGadget.hpp" #include "tket/Converters/PhasePoly.hpp" #include "tket/Diagonalisation/Diagonalisation.hpp" #include "tket/Ops/OpJsonFactory.hpp" @@ -61,7 +60,11 @@ Op_ptr PauliExpBox::symbol_substitution( } void PauliExpBox::generate_circuit() const { - Circuit circ = pauli_gadget(paulis_.string, paulis_.coeff, cx_config_); + // paulis_ gets cast to a sparse form, so circuit from pauli_gadget will only + // contain qubits with {X, Y, Z}; appending it to a blank circuit containing + // all qubits makes the size of the circuit fixed + Circuit circ(paulis_.size()); + circ.append(pauli_gadget(paulis_, cx_config_)); circ_ = std::make_shared(circ); } @@ -149,8 +152,12 @@ Op_ptr PauliExpPairBox::symbol_substitution( } void PauliExpPairBox::generate_circuit() const { - Circuit circ = Circuit(paulis0_.size()); - append_pauli_gadget_pair(circ, paulis0_, paulis1_, cx_config_); + // paulis0_ and paulis1_ gets cast to a sparse form, so circuit from + // pauli_gadget_pair will only contain qubits with {X, Y, Z} on at least one; + // appending it to a blank circuit containing all qubits makes the size of the + // circuit fixed + Circuit circ(paulis0_.size()); + circ.append(pauli_gadget_pair(paulis0_, paulis1_, cx_config_)); circ_ = std::make_shared(circ); } @@ -306,7 +313,7 @@ void PauliExpCommutingSetBox::generate_circuit() const { Circuit phase_poly_circ(n_qubits); for (const SpSymPauliTensor &pgp : gadgets) { - append_single_pauli_gadget(phase_poly_circ, pgp); + phase_poly_circ.append(pauli_gadget(pgp, CXConfigType::Snake)); } phase_poly_circ.decompose_boxes_recursively(); PhasePolyBox ppbox(phase_poly_circ); @@ -638,4 +645,79 @@ void TermSequenceBox::generate_circuit() const { REGISTER_OPFACTORY(TermSequenceBox, TermSequenceBox) +void append_single_pauli_gadget_as_pauli_exp_box( + Circuit &circ, const SpSymPauliTensor &pauli, CXConfigType cx_config) { + std::vector string; + std::vector mapping; + for (const std::pair &term : pauli.string) { + string.push_back(term.second); + mapping.push_back(term.first); + } + PauliExpBox box(SymPauliTensor(string, pauli.coeff), cx_config); + circ.add_box(box, mapping); +} + +void append_pauli_gadget_pair_as_box( + Circuit &circ, const SpSymPauliTensor &pauli0, + const SpSymPauliTensor &pauli1, CXConfigType cx_config) { + std::vector mapping; + std::vector paulis0; + std::vector paulis1; + QubitPauliMap p1map = pauli1.string; + // add paulis for qubits in pauli0_string + for (const std::pair &term : pauli0.string) { + mapping.push_back(term.first); + paulis0.push_back(term.second); + auto found = p1map.find(term.first); + if (found == p1map.end()) { + paulis1.push_back(Pauli::I); + } else { + paulis1.push_back(found->second); + p1map.erase(found); + } + } + // add paulis for qubits in pauli1_string that weren't in pauli0_string + for (const std::pair &term : p1map) { + mapping.push_back(term.first); + paulis1.push_back(term.second); + paulis0.push_back(Pauli::I); // If pauli0_string contained qubit, would + // have been handled above + } + PauliExpPairBox box( + SymPauliTensor(paulis0, pauli0.coeff), + SymPauliTensor(paulis1, pauli1.coeff), cx_config); + circ.add_box(box, mapping); +} + +void append_commuting_pauli_gadget_set_as_box( + Circuit &circ, const std::list &gadgets, + CXConfigType cx_config) { + // Translate from QubitPauliTensors to vectors of Paulis of same length + // Preserves ordering of qubits + + std::set all_qubits; + for (const SpSymPauliTensor &gadget : gadgets) { + for (const std::pair &qubit_pauli : gadget.string) { + all_qubits.insert(qubit_pauli.first); + } + } + + std::vector mapping; + for (const auto &qubit : all_qubits) { + mapping.push_back(qubit); + } + + std::vector pauli_gadgets; + for (const SpSymPauliTensor &gadget : gadgets) { + SymPauliTensor &new_gadget = + pauli_gadgets.emplace_back(DensePauliMap{}, gadget.coeff); + for (const Qubit &qubit : mapping) { + new_gadget.string.push_back(gadget.get(qubit)); + } + } + + PauliExpCommutingSetBox box(pauli_gadgets, cx_config); + circ.add_box(box, mapping); +} + } // namespace tket diff --git a/tket/src/Clifford/ChoiMixTableau.cpp b/tket/src/Clifford/ChoiMixTableau.cpp index 77c8ef02a4..ef0b08410e 100644 --- a/tket/src/Clifford/ChoiMixTableau.cpp +++ b/tket/src/Clifford/ChoiMixTableau.cpp @@ -103,11 +103,13 @@ ChoiMixTableau::ChoiMixTableau(const std::list& rows) VectorXb phase = VectorXb::Zero(n_rows); unsigned r = 0; for (const row_tensor_t& row : rows) { + unsigned n_ys = 0; for (const std::pair& qb : row.first.string) { unsigned c = col_index_.left.at(col_key_t{qb.first, TableauSegment::Input}); if (qb.second == Pauli::X || qb.second == Pauli::Y) xmat(r, c) = true; if (qb.second == Pauli::Z || qb.second == Pauli::Y) zmat(r, c) = true; + if (qb.second == Pauli::Y) ++n_ys; } for (const std::pair& qb : row.second.string) { unsigned c = @@ -115,7 +117,8 @@ ChoiMixTableau::ChoiMixTableau(const std::list& rows) if (qb.second == Pauli::X || qb.second == Pauli::Y) xmat(r, c) = true; if (qb.second == Pauli::Z || qb.second == Pauli::Y) zmat(r, c) = true; } - phase(r) = row.first.is_real_negative() ^ row.second.is_real_negative(); + phase(r) = row.first.is_real_negative() ^ row.second.is_real_negative() ^ + (n_ys % 2 == 1); ++r; } tab_ = SymplecticTableau(xmat, zmat, phase); @@ -125,22 +128,30 @@ unsigned ChoiMixTableau::get_n_rows() const { return tab_.get_n_rows(); } unsigned ChoiMixTableau::get_n_boundaries() const { return col_index_.size(); } -unsigned ChoiMixTableau::get_n_inputs() const { - unsigned n = 0; +unsigned ChoiMixTableau::get_n_inputs() const { return input_qubits().size(); } + +unsigned ChoiMixTableau::get_n_outputs() const { + return output_qubits().size(); +} + +qubit_vector_t ChoiMixTableau::input_qubits() const { + qubit_vector_t ins; BOOST_FOREACH ( tableau_col_index_t::left_const_reference entry, col_index_.left) { - if (entry.first.second == TableauSegment::Input) ++n; + if (entry.first.second == TableauSegment::Input) + ins.push_back(entry.first.first); } - return n; + return ins; } -unsigned ChoiMixTableau::get_n_outputs() const { - unsigned n = 0; +qubit_vector_t ChoiMixTableau::output_qubits() const { + qubit_vector_t outs; BOOST_FOREACH ( tableau_col_index_t::left_const_reference entry, col_index_.left) { - if (entry.first.second == TableauSegment::Output) ++n; + if (entry.first.second == TableauSegment::Output) + outs.push_back(entry.first.first); } - return n; + return outs; } ChoiMixTableau::row_tensor_t ChoiMixTableau::stab_to_row_tensor( @@ -173,17 +184,22 @@ PauliStabiliser ChoiMixTableau::row_tensor_to_stab( } ChoiMixTableau::row_tensor_t ChoiMixTableau::get_row(unsigned i) const { - return stab_to_row_tensor(tab_.get_pauli(i)); + ChoiMixTableau::row_tensor_t res = stab_to_row_tensor(tab_.get_pauli(i)); + res.first.transpose(); + res.second.coeff = (res.first.coeff + res.second.coeff) % 4; + res.first.coeff = 0; + return res; } ChoiMixTableau::row_tensor_t ChoiMixTableau::get_row_product( const std::vector& rows) const { row_tensor_t result = {{}, {}}; for (unsigned i : rows) { - row_tensor_t row_i = get_row(i); + row_tensor_t row_i = stab_to_row_tensor(tab_.get_pauli(i)); result.first = result.first * row_i.first; result.second = result.second * row_i.second; } + result.first.transpose(); result.second.coeff = (result.first.coeff + result.second.coeff) % 4; result.first.coeff = 0; return result; @@ -194,11 +210,26 @@ void ChoiMixTableau::apply_S(const Qubit& qb, TableauSegment seg) { tab_.apply_S(col); } +void ChoiMixTableau::apply_Z(const Qubit& qb, TableauSegment seg) { + unsigned col = col_index_.left.at(col_key_t{qb, seg}); + tab_.apply_Z(col); +} + void ChoiMixTableau::apply_V(const Qubit& qb, TableauSegment seg) { unsigned col = col_index_.left.at(col_key_t{qb, seg}); tab_.apply_V(col); } +void ChoiMixTableau::apply_X(const Qubit& qb, TableauSegment seg) { + unsigned col = col_index_.left.at(col_key_t{qb, seg}); + tab_.apply_X(col); +} + +void ChoiMixTableau::apply_H(const Qubit& qb, TableauSegment seg) { + unsigned col = col_index_.left.at(col_key_t{qb, seg}); + tab_.apply_H(col); +} + void ChoiMixTableau::apply_CX( const Qubit& control, const Qubit& target, TableauSegment seg) { unsigned uc = col_index_.left.at(col_key_t{control, seg}); @@ -210,20 +241,16 @@ void ChoiMixTableau::apply_gate( OpType type, const qubit_vector_t& qbs, TableauSegment seg) { switch (type) { case OpType::Z: { - apply_S(qbs.at(0), seg); - apply_S(qbs.at(0), seg); + apply_Z(qbs.at(0), seg); break; } case OpType::X: { - apply_V(qbs.at(0), seg); - apply_V(qbs.at(0), seg); + apply_X(qbs.at(0), seg); break; } case OpType::Y: { - apply_S(qbs.at(0), seg); - apply_S(qbs.at(0), seg); - apply_V(qbs.at(0), seg); - apply_V(qbs.at(0), seg); + apply_Z(qbs.at(0), seg); + apply_X(qbs.at(0), seg); break; } case OpType::S: { @@ -232,8 +259,7 @@ void ChoiMixTableau::apply_gate( } case OpType::Sdg: { apply_S(qbs.at(0), seg); - apply_S(qbs.at(0), seg); - apply_S(qbs.at(0), seg); + apply_Z(qbs.at(0), seg); break; } case OpType::SX: @@ -244,14 +270,11 @@ void ChoiMixTableau::apply_gate( case OpType::SXdg: case OpType::Vdg: { apply_V(qbs.at(0), seg); - apply_V(qbs.at(0), seg); - apply_V(qbs.at(0), seg); + apply_X(qbs.at(0), seg); break; } case OpType::H: { - apply_S(qbs.at(0), seg); - apply_V(qbs.at(0), seg); - apply_S(qbs.at(0), seg); + apply_H(qbs.at(0), seg); break; } case OpType::CX: { @@ -263,25 +286,19 @@ void ChoiMixTableau::apply_gate( apply_S(qbs.at(1), seg); apply_CX(qbs.at(0), qbs.at(1), seg); apply_S(qbs.at(1), seg); - apply_S(qbs.at(1), seg); - apply_S(qbs.at(1), seg); + apply_Z(qbs.at(1), seg); } else { apply_S(qbs.at(1), seg); - apply_S(qbs.at(1), seg); - apply_S(qbs.at(1), seg); + apply_Z(qbs.at(1), seg); apply_CX(qbs.at(0), qbs.at(1), seg); apply_S(qbs.at(1), seg); } break; } case OpType::CZ: { - apply_S(qbs.at(1), seg); - apply_V(qbs.at(1), seg); - apply_S(qbs.at(1), seg); + apply_H(qbs.at(1), seg); apply_CX(qbs.at(0), qbs.at(1), seg); - apply_S(qbs.at(1), seg); - apply_V(qbs.at(1), seg); - apply_S(qbs.at(1), seg); + apply_H(qbs.at(1), seg); break; } case OpType::ZZMax: { @@ -292,16 +309,14 @@ void ChoiMixTableau::apply_gate( } case OpType::ECR: { if (seg == TableauSegment::Input) { - apply_V(qbs.at(0), seg); - apply_V(qbs.at(0), seg); + apply_X(qbs.at(0), seg); apply_S(qbs.at(0), seg); apply_V(qbs.at(1), seg); apply_CX(qbs.at(0), qbs.at(1), seg); } else { apply_CX(qbs.at(0), qbs.at(1), seg); apply_S(qbs.at(0), seg); - apply_V(qbs.at(0), seg); - apply_V(qbs.at(0), seg); + apply_X(qbs.at(0), seg); apply_V(qbs.at(1), seg); } break; @@ -312,8 +327,7 @@ void ChoiMixTableau::apply_gate( apply_CX(qbs.at(0), qbs.at(1), seg); apply_V(qbs.at(0), seg); apply_S(qbs.at(1), seg); - apply_S(qbs.at(1), seg); - apply_S(qbs.at(1), seg); + apply_Z(qbs.at(1), seg); apply_CX(qbs.at(0), qbs.at(1), seg); apply_V(qbs.at(0), seg); apply_V(qbs.at(1), seg); @@ -341,28 +355,25 @@ void ChoiMixTableau::apply_gate( // reinsert qubit initialised to maximally mixed state (no coherent // stabilizers) col_index_.insert({{qbs.at(0), TableauSegment::Input}, col}); - tab_.xmat_.conservativeResize(rows, col + 1); - tab_.xmat_.col(col) = MatrixXb::Zero(rows, 1); - tab_.zmat_.conservativeResize(rows, col + 1); - tab_.zmat_.col(col) = MatrixXb::Zero(rows, 1); - ++tab_.n_qubits_; + tab_.xmat.conservativeResize(rows, col + 1); + tab_.xmat.col(col) = MatrixXb::Zero(rows, 1); + tab_.zmat.conservativeResize(rows, col + 1); + tab_.zmat.col(col) = MatrixXb::Zero(rows, 1); } else { discard_qubit(qbs.at(0), TableauSegment::Output); unsigned col = get_n_boundaries(); unsigned rows = get_n_rows(); // reinsert qubit initialised to |0> (add a Z stabilizer) col_index_.insert({{qbs.at(0), TableauSegment::Output}, col}); - tab_.xmat_.conservativeResize(rows + 1, col + 1); - tab_.xmat_.col(col) = MatrixXb::Zero(rows + 1, 1); - tab_.xmat_.row(rows) = MatrixXb::Zero(1, col + 1); - tab_.zmat_.conservativeResize(rows + 1, col + 1); - tab_.zmat_.col(col) = MatrixXb::Zero(rows + 1, 1); - tab_.zmat_.row(rows) = MatrixXb::Zero(1, col + 1); - tab_.zmat_(rows, col) = true; - tab_.phase_.conservativeResize(rows + 1); - tab_.phase_(rows) = false; - ++tab_.n_rows_; - ++tab_.n_qubits_; + tab_.xmat.conservativeResize(rows + 1, col + 1); + tab_.xmat.col(col) = MatrixXb::Zero(rows + 1, 1); + tab_.xmat.row(rows) = MatrixXb::Zero(1, col + 1); + tab_.zmat.conservativeResize(rows + 1, col + 1); + tab_.zmat.col(col) = MatrixXb::Zero(rows + 1, 1); + tab_.zmat.row(rows) = MatrixXb::Zero(1, col + 1); + tab_.zmat(rows, col) = true; + tab_.phase.conservativeResize(rows + 1); + tab_.phase(rows) = false; } break; } @@ -399,10 +410,10 @@ void ChoiMixTableau::post_select(const Qubit& qb, TableauSegment seg) { unsigned n_cols = get_n_boundaries(); unsigned col = col_index_.left.at(col_key_t{qb, seg}); for (unsigned r = 0; r < n_rows; ++r) { - if (tab_.zmat_(r, col)) { + if (tab_.zmat(r, col)) { bool only_z = true; for (unsigned c = 0; c < n_cols; ++c) { - if ((tab_.xmat_(r, c) || tab_.zmat_(r, c)) && (c != col)) { + if ((tab_.xmat(r, c) || tab_.zmat(r, c)) && (c != col)) { only_z = false; break; } @@ -410,7 +421,7 @@ void ChoiMixTableau::post_select(const Qubit& qb, TableauSegment seg) { if (!only_z) break; // Not deterministic // From here, we know we are in a deterministic case // If deterministically fail, throw an exception - if (tab_.phase_(r)) + if (tab_.phase(r)) throw std::logic_error( "Post-selecting a tableau fails deterministically"); // Otherwise, we succeed and remove the stabilizer @@ -423,7 +434,7 @@ void ChoiMixTableau::post_select(const Qubit& qb, TableauSegment seg) { // Isolate a single row with an X (if one exists) std::optional x_row = std::nullopt; for (unsigned r = 0; r < n_rows; ++r) { - if (tab_.xmat_(r, col)) { + if (tab_.xmat(r, col)) { if (x_row) { // Already found another row with an X, so combine them tab_.row_mult(*x_row, r); @@ -446,7 +457,7 @@ void ChoiMixTableau::discard_qubit(const Qubit& qb, TableauSegment seg) { // Isolate a single row with an X (if one exists) std::optional x_row = std::nullopt; for (unsigned r = 0; r < get_n_rows(); ++r) { - if (tab_.xmat_(r, col)) { + if (tab_.xmat(r, col)) { if (x_row) { // Already found another row with an X, so combine them tab_.row_mult(*x_row, r); @@ -464,7 +475,7 @@ void ChoiMixTableau::discard_qubit(const Qubit& qb, TableauSegment seg) { // Isolate a single row with a Z (if one exists) std::optional z_row = std::nullopt; for (unsigned r = 0; r < get_n_rows(); ++r) { - if (tab_.zmat_(r, col)) { + if (tab_.zmat(r, col)) { if (z_row) { // Already found another row with a Z, so combine them tab_.row_mult(*z_row, r); @@ -487,7 +498,7 @@ void ChoiMixTableau::collapse_qubit(const Qubit& qb, TableauSegment seg) { // Isolate a single row with an X (if one exists) std::optional x_row = std::nullopt; for (unsigned r = 0; r < get_n_rows(); ++r) { - if (tab_.xmat_(r, col)) { + if (tab_.xmat(r, col)) { if (x_row) { // Already found another row with an X, so combine them tab_.row_mult(*x_row, r); @@ -512,14 +523,13 @@ void ChoiMixTableau::remove_row(unsigned row) { unsigned n_rows = get_n_rows(); unsigned n_cols = get_n_boundaries(); if (row < n_rows - 1) { - tab_.xmat_.row(row) = tab_.xmat_.row(n_rows - 1); - tab_.zmat_.row(row) = tab_.zmat_.row(n_rows - 1); - tab_.phase_(row) = tab_.phase_(n_rows - 1); + tab_.xmat.row(row) = tab_.xmat.row(n_rows - 1); + tab_.zmat.row(row) = tab_.zmat.row(n_rows - 1); + tab_.phase(row) = tab_.phase(n_rows - 1); } - tab_.xmat_.conservativeResize(n_rows - 1, n_cols); - tab_.zmat_.conservativeResize(n_rows - 1, n_cols); - tab_.phase_.conservativeResize(n_rows - 1); - --tab_.n_rows_; + tab_.xmat.conservativeResize(n_rows - 1, n_cols); + tab_.zmat.conservativeResize(n_rows - 1, n_cols); + tab_.phase.conservativeResize(n_rows - 1); } void ChoiMixTableau::remove_col(unsigned col) { @@ -530,11 +540,11 @@ void ChoiMixTableau::remove_col(unsigned col) { unsigned n_rows = get_n_rows(); unsigned n_cols = get_n_boundaries(); if (col < n_cols - 1) { - tab_.xmat_.col(col) = tab_.xmat_.col(n_cols - 1); - tab_.zmat_.col(col) = tab_.zmat_.col(n_cols - 1); + tab_.xmat.col(col) = tab_.xmat.col(n_cols - 1); + tab_.zmat.col(col) = tab_.zmat.col(n_cols - 1); } - tab_.xmat_.conservativeResize(n_rows, n_cols - 1); - tab_.zmat_.conservativeResize(n_rows, n_cols - 1); + tab_.xmat.conservativeResize(n_rows, n_cols - 1); + tab_.zmat.conservativeResize(n_rows, n_cols - 1); col_index_.right.erase(col); if (col < n_cols - 1) { tableau_col_index_t::right_iterator it = col_index_.right.find(n_cols - 1); @@ -542,7 +552,6 @@ void ChoiMixTableau::remove_col(unsigned col) { col_index_.right.erase(it); col_index_.insert({last, col}); } - --tab_.n_qubits_; } void ChoiMixTableau::canonical_column_order(TableauSegment first) { @@ -579,10 +588,10 @@ void ChoiMixTableau::canonical_column_order(TableauSegment first) { for (unsigned j = 0; j < i; ++j) { col_key_t key = new_index.right.at(j); unsigned c = col_index_.left.at(key); - xmat.col(j) = tab_.xmat_.col(c); - zmat.col(j) = tab_.zmat_.col(c); + xmat.col(j) = tab_.xmat.col(c); + zmat.col(j) = tab_.zmat.col(c); } - tab_ = SymplecticTableau(xmat, zmat, tab_.phase_); + tab_ = SymplecticTableau(xmat, zmat, tab_.phase); col_index_ = new_index; } @@ -620,12 +629,12 @@ ChoiMixTableau ChoiMixTableau::compose( } MatrixXb fullx(f_rows + s_rows, f_cols + s_cols), fullz(f_rows + s_rows, f_cols + s_cols); - fullx << first.tab_.xmat_, MatrixXb::Zero(f_rows, s_cols), - MatrixXb::Zero(s_rows, f_cols), second.tab_.xmat_; - fullz << first.tab_.zmat_, MatrixXb::Zero(f_rows, s_cols), - MatrixXb::Zero(s_rows, f_cols), second.tab_.zmat_; + fullx << first.tab_.xmat, MatrixXb::Zero(f_rows, s_cols), + MatrixXb::Zero(s_rows, f_cols), second.tab_.xmat; + fullz << first.tab_.zmat, MatrixXb::Zero(f_rows, s_cols), + MatrixXb::Zero(s_rows, f_cols), second.tab_.zmat; VectorXb fullph(f_rows + s_rows); - fullph << first.tab_.phase_, second.tab_.phase_; + fullph << first.tab_.phase, second.tab_.phase; ChoiMixTableau combined(fullx, fullz, fullph, 0); // For each connecting pair of qubits, compose via a Bell post-selection for (unsigned i = 0; i < f_cols; ++i) { diff --git a/tket/src/Clifford/SymplecticTableau.cpp b/tket/src/Clifford/SymplecticTableau.cpp index 5c2cd7513a..4c15b44d1f 100644 --- a/tket/src/Clifford/SymplecticTableau.cpp +++ b/tket/src/Clifford/SymplecticTableau.cpp @@ -62,56 +62,51 @@ const std::map, std::pair> SymplecticTableau::SymplecticTableau( const MatrixXb &xmat, const MatrixXb &zmat, const VectorXb &phase) - : n_rows_(xmat.rows()), - n_qubits_(xmat.cols()), - xmat_(xmat), - zmat_(zmat), - phase_(phase) { - if (zmat.rows() != n_rows_ || phase_.size() != n_rows_) + : xmat(xmat), zmat(zmat), phase(phase) { + if (zmat.rows() != xmat.rows() || phase.size() != xmat.rows()) throw std::invalid_argument( "Tableau must have the same number of rows in each component."); - if (zmat.cols() != n_qubits_) + if (zmat.cols() != xmat.cols()) throw std::invalid_argument( "Tableau must have the same number of columns in x and z components."); } SymplecticTableau::SymplecticTableau(const PauliStabiliserVec &rows) { - n_rows_ = rows.size(); - if (n_rows_ == 0) - n_qubits_ = 0; - else - n_qubits_ = rows[0].string.size(); - xmat_ = MatrixXb::Zero(n_rows_, n_qubits_); - zmat_ = MatrixXb::Zero(n_rows_, n_qubits_); - phase_ = VectorXb::Zero(n_rows_); - for (unsigned i = 0; i < n_rows_; ++i) { + unsigned n_rows = rows.size(); + unsigned n_qubits = 0; + if (n_rows != 0) n_qubits = rows[0].string.size(); + xmat = MatrixXb::Zero(n_rows, n_qubits); + zmat = MatrixXb::Zero(n_rows, n_qubits); + phase = VectorXb::Zero(n_rows); + for (unsigned i = 0; i < n_rows; ++i) { const PauliStabiliser &stab = rows[i]; - if (stab.string.size() != n_qubits_) + if (stab.string.size() != n_qubits) throw std::invalid_argument( "Tableau must have the same number of qubits in each row."); - for (unsigned q = 0; q < n_qubits_; ++q) { + for (unsigned q = 0; q < n_qubits; ++q) { const Pauli &p = stab.get(q); - xmat_(i, q) = (p == Pauli::X) || (p == Pauli::Y); - zmat_(i, q) = (p == Pauli::Z) || (p == Pauli::Y); + xmat(i, q) = (p == Pauli::X) || (p == Pauli::Y); + zmat(i, q) = (p == Pauli::Z) || (p == Pauli::Y); } - phase_(i) = stab.is_real_negative(); + phase(i) = stab.is_real_negative(); } } -unsigned SymplecticTableau::get_n_rows() const { return n_rows_; } -unsigned SymplecticTableau::get_n_qubits() const { return n_qubits_; } +unsigned SymplecticTableau::get_n_rows() const { return xmat.rows(); } +unsigned SymplecticTableau::get_n_qubits() const { return xmat.cols(); } PauliStabiliser SymplecticTableau::get_pauli(unsigned i) const { - std::vector str(n_qubits_); - for (unsigned q = 0; q < n_qubits_; ++q) { - str[q] = BoolPauli{xmat_(i, q), zmat_(i, q)}.to_pauli(); + unsigned n_qubits = get_n_qubits(); + std::vector str(n_qubits); + for (unsigned q = 0; q < n_qubits; ++q) { + str[q] = BoolPauli{xmat(i, q), zmat(i, q)}.to_pauli(); } - return PauliStabiliser(str, phase_(i) ? 2 : 0); + return PauliStabiliser(str, phase(i) ? 2 : 0); } std::ostream &operator<<(std::ostream &os, const SymplecticTableau &tab) { - for (unsigned i = 0; i < tab.n_rows_; ++i) { - os << tab.xmat_.row(i) << " " << tab.zmat_.row(i) << " " << tab.phase_(i) + for (unsigned i = 0; i < tab.get_n_rows(); ++i) { + os << tab.xmat.row(i) << " " << tab.zmat.row(i) << " " << tab.phase(i) << std::endl; } return os; @@ -120,40 +115,58 @@ std::ostream &operator<<(std::ostream &os, const SymplecticTableau &tab) { bool SymplecticTableau::operator==(const SymplecticTableau &other) const { // Need this to short-circuit before matrix checks as comparing matrices of // different sizes will throw an exception - return (this->n_rows_ == other.n_rows_) && - (this->n_qubits_ == other.n_qubits_) && (this->xmat_ == other.xmat_) && - (this->zmat_ == other.zmat_) && (this->phase_ == other.phase_); + return (this->get_n_rows() == other.get_n_rows()) && + (this->get_n_qubits() == other.get_n_qubits()) && + (this->xmat == other.xmat) && (this->zmat == other.zmat) && + (this->phase == other.phase); } void SymplecticTableau::row_mult(unsigned ra, unsigned rw, Complex coeff) { - MatrixXb::RowXpr xa = xmat_.row(ra); - MatrixXb::RowXpr za = zmat_.row(ra); - MatrixXb::RowXpr xw = xmat_.row(rw); - MatrixXb::RowXpr zw = zmat_.row(rw); - row_mult(xa, za, phase_(ra), xw, zw, phase_(rw), coeff, xw, zw, phase_(rw)); + MatrixXb::RowXpr xa = xmat.row(ra); + MatrixXb::RowXpr za = zmat.row(ra); + MatrixXb::RowXpr xw = xmat.row(rw); + MatrixXb::RowXpr zw = zmat.row(rw); + row_mult(xa, za, phase(ra), xw, zw, phase(rw), coeff, xw, zw, phase(rw)); } void SymplecticTableau::apply_S(unsigned qb) { - MatrixXb::ColXpr xcol = xmat_.col(qb); - MatrixXb::ColXpr zcol = zmat_.col(qb); - col_mult(xcol, zcol, false, zcol, phase_); + MatrixXb::ColXpr xcol = xmat.col(qb); + MatrixXb::ColXpr zcol = zmat.col(qb); + col_mult(xcol, zcol, false, zcol, phase); +} + +void SymplecticTableau::apply_Z(unsigned qb) { + for (unsigned i = 0; i < get_n_rows(); ++i) phase(i) = phase(i) ^ xmat(i, qb); } void SymplecticTableau::apply_V(unsigned qb) { - MatrixXb::ColXpr xcol = xmat_.col(qb); - MatrixXb::ColXpr zcol = zmat_.col(qb); - col_mult(zcol, xcol, true, xcol, phase_); + MatrixXb::ColXpr xcol = xmat.col(qb); + MatrixXb::ColXpr zcol = zmat.col(qb); + col_mult(zcol, xcol, true, xcol, phase); +} + +void SymplecticTableau::apply_X(unsigned qb) { + for (unsigned i = 0; i < get_n_rows(); ++i) phase(i) = phase(i) ^ zmat(i, qb); +} + +void SymplecticTableau::apply_H(unsigned qb) { + for (unsigned i = 0; i < get_n_rows(); ++i) { + phase(i) = phase(i) ^ (xmat(i, qb) && zmat(i, qb)); + bool temp = xmat(i, qb); + xmat(i, qb) = zmat(i, qb); + zmat(i, qb) = temp; + } } void SymplecticTableau::apply_CX(unsigned qc, unsigned qt) { if (qc == qt) throw std::logic_error( "Attempting to apply a CX with equal control and target in a tableau"); - for (unsigned i = 0; i < n_rows_; ++i) { - phase_(i) = phase_(i) ^ (xmat_(i, qc) && zmat_(i, qt) && - !(xmat_(i, qt) ^ zmat_(i, qc))); - xmat_(i, qt) = xmat_(i, qc) ^ xmat_(i, qt); - zmat_(i, qc) = zmat_(i, qc) ^ zmat_(i, qt); + for (unsigned i = 0; i < get_n_rows(); ++i) { + phase(i) = + phase(i) ^ (xmat(i, qc) && zmat(i, qt) && !(xmat(i, qt) ^ zmat(i, qc))); + xmat(i, qt) = xmat(i, qc) ^ xmat(i, qt); + zmat(i, qc) = zmat(i, qc) ^ zmat(i, qt); } } @@ -161,20 +174,16 @@ void SymplecticTableau::apply_gate( OpType type, const std::vector &qbs) { switch (type) { case OpType::Z: { - apply_S(qbs.at(0)); - apply_S(qbs.at(0)); + apply_Z(qbs.at(0)); break; } case OpType::X: { - apply_V(qbs.at(0)); - apply_V(qbs.at(0)); + apply_X(qbs.at(0)); break; } case OpType::Y: { - apply_S(qbs.at(0)); - apply_S(qbs.at(0)); - apply_V(qbs.at(0)); - apply_V(qbs.at(0)); + apply_Z(qbs.at(0)); + apply_X(qbs.at(0)); break; } case OpType::S: { @@ -183,8 +192,7 @@ void SymplecticTableau::apply_gate( } case OpType::Sdg: { apply_S(qbs.at(0)); - apply_S(qbs.at(0)); - apply_S(qbs.at(0)); + apply_Z(qbs.at(0)); break; } case OpType::V: @@ -195,14 +203,11 @@ void SymplecticTableau::apply_gate( case OpType::Vdg: case OpType::SXdg: { apply_V(qbs.at(0)); - apply_V(qbs.at(0)); - apply_V(qbs.at(0)); + apply_X(qbs.at(0)); break; } case OpType::H: { - apply_S(qbs.at(0)); - apply_V(qbs.at(0)); - apply_S(qbs.at(0)); + apply_H(qbs.at(0)); break; } case OpType::CX: { @@ -211,63 +216,43 @@ void SymplecticTableau::apply_gate( } case OpType::CY: { apply_S(qbs.at(1)); - apply_S(qbs.at(1)); - apply_S(qbs.at(1)); + apply_Z(qbs.at(1)); apply_CX(qbs.at(0), qbs.at(1)); apply_S(qbs.at(1)); break; } case OpType::CZ: { - apply_S(qbs.at(1)); - apply_V(qbs.at(1)); - apply_S(qbs.at(1)); + apply_H(qbs.at(1)); apply_CX(qbs.at(0), qbs.at(1)); - apply_S(qbs.at(1)); - apply_V(qbs.at(1)); - apply_S(qbs.at(1)); + apply_H(qbs.at(1)); break; } case OpType::ZZMax: { + apply_H(qbs.at(1)); apply_S(qbs.at(0)); - apply_S(qbs.at(1)); - apply_S(qbs.at(1)); - apply_S(qbs.at(1)); apply_V(qbs.at(1)); - apply_S(qbs.at(1)); apply_CX(qbs.at(0), qbs.at(1)); - apply_S(qbs.at(1)); - apply_V(qbs.at(1)); + apply_H(qbs.at(1)); break; } case OpType::ECR: { apply_S(qbs.at(0)); - apply_S(qbs.at(0)); - apply_V(qbs.at(0)); - apply_V(qbs.at(0)); - apply_S(qbs.at(0)); - apply_V(qbs.at(1)); - apply_V(qbs.at(1)); + apply_X(qbs.at(0)); apply_V(qbs.at(1)); + apply_X(qbs.at(1)); apply_CX(qbs.at(0), qbs.at(1)); break; } case OpType::ISWAPMax: { - apply_S(qbs.at(0)); - apply_S(qbs.at(0)); - apply_S(qbs.at(1)); - apply_S(qbs.at(1)); apply_V(qbs.at(0)); - apply_V(qbs.at(0)); - apply_V(qbs.at(0)); - apply_V(qbs.at(1)); - apply_V(qbs.at(1)); apply_V(qbs.at(1)); apply_CX(qbs.at(0), qbs.at(1)); apply_V(qbs.at(0)); apply_S(qbs.at(1)); - apply_V(qbs.at(1)); + apply_Z(qbs.at(1)); apply_CX(qbs.at(0), qbs.at(1)); apply_V(qbs.at(0)); + apply_V(qbs.at(1)); break; } case OpType::SWAP: { @@ -294,7 +279,8 @@ void SymplecticTableau::apply_gate( void SymplecticTableau::apply_pauli_gadget( const PauliStabiliser &pauli, unsigned half_pis) { - if (pauli.string.size() != n_qubits_) { + unsigned n_qubits = get_n_qubits(); + if (pauli.string.size() != n_qubits) { throw std::invalid_argument( "Cannot apply pauli gadget to SymplecticTableau; string and tableau " "have different numbers of qubits"); @@ -326,39 +312,40 @@ void SymplecticTableau::apply_pauli_gadget( // From here, half_pis == 1 or 3 // They act the same except for a phase flip on the product term - MatrixXb pauli_xrow = MatrixXb::Zero(1, n_qubits_); - MatrixXb pauli_zrow = MatrixXb::Zero(1, n_qubits_); - for (unsigned i = 0; i < n_qubits_; ++i) { + MatrixXb pauli_xrow = MatrixXb::Zero(1, n_qubits); + MatrixXb pauli_zrow = MatrixXb::Zero(1, n_qubits); + for (unsigned i = 0; i < n_qubits; ++i) { Pauli p = pauli.string.at(i); pauli_xrow(i) = (p == Pauli::X) || (p == Pauli::Y); pauli_zrow(i) = (p == Pauli::Z) || (p == Pauli::Y); } - bool phase = pauli.is_real_negative() ^ (half_pis == 3); + bool phase_flip = pauli.is_real_negative() ^ (half_pis == 3); - for (unsigned i = 0; i < n_rows_; ++i) { + for (unsigned i = 0; i < get_n_rows(); ++i) { bool anti = false; - MatrixXb::RowXpr xr = xmat_.row(i); - MatrixXb::RowXpr zr = zmat_.row(i); - for (unsigned q = 0; q < n_qubits_; ++q) { + MatrixXb::RowXpr xr = xmat.row(i); + MatrixXb::RowXpr zr = zmat.row(i); + for (unsigned q = 0; q < n_qubits; ++q) { anti ^= (xr(q) && pauli_zrow(q)); anti ^= (zr(q) && pauli_xrow(q)); } if (anti) { row_mult( - xr, zr, phase_(i), pauli_xrow.row(0), pauli_zrow.row(0), phase, i_, - xr, zr, phase_(i)); + xr, zr, phase(i), pauli_xrow.row(0), pauli_zrow.row(0), phase_flip, + i_, xr, zr, phase(i)); } } } MatrixXb SymplecticTableau::anticommuting_rows() const { - MatrixXb res = MatrixXb::Zero(n_rows_, n_rows_); - for (unsigned i = 0; i < n_rows_; ++i) { + unsigned n_rows = get_n_rows(); + MatrixXb res = MatrixXb::Zero(n_rows, n_rows); + for (unsigned i = 0; i < n_rows; ++i) { for (unsigned j = 0; j < i; ++j) { bool anti = false; - for (unsigned q = 0; q < n_qubits_; ++q) { - anti ^= (xmat_(i, q) && zmat_(j, q)); - anti ^= (xmat_(j, q) && zmat_(i, q)); + for (unsigned q = 0; q < get_n_qubits(); ++q) { + anti ^= (xmat(i, q) && zmat(j, q)); + anti ^= (xmat(j, q) && zmat(i, q)); } res(i, j) = anti; res(j, i) = anti; @@ -372,32 +359,33 @@ unsigned SymplecticTableau::rank() const { SymplecticTableau copy(*this); copy.gaussian_form(); unsigned empty_rows = 0; - for (unsigned i = 0; i < n_rows_; ++i) { - if (copy.xmat_.row(n_rows_ - 1 - i).isZero() && - copy.zmat_.row(n_rows_ - 1 - i).isZero()) + unsigned n_rows = get_n_rows(); + for (unsigned i = 0; i < n_rows; ++i) { + if (copy.xmat.row(n_rows - 1 - i).isZero() && + copy.zmat.row(n_rows - 1 - i).isZero()) ++empty_rows; else break; } - return n_rows_ - empty_rows; + return n_rows - empty_rows; } SymplecticTableau SymplecticTableau::conjugate() const { SymplecticTableau conj(*this); - for (unsigned i = 0; i < n_rows_; ++i) { + for (unsigned i = 0; i < get_n_rows(); ++i) { unsigned sum = 0; - for (unsigned j = 0; j < n_qubits_; ++j) { - if (xmat_(i, j) && zmat_(i, j)) ++sum; + for (unsigned j = 0; j < get_n_qubits(); ++j) { + if (xmat(i, j) && zmat(i, j)) ++sum; } - if (sum % 2 == 1) conj.phase_(i) ^= true; + if (sum % 2 == 1) conj.phase(i) ^= true; } return conj; } void SymplecticTableau::gaussian_form() { - MatrixXb fullmat = MatrixXb::Zero(n_rows_, 2 * n_qubits_); - fullmat(Eigen::all, Eigen::seq(0, Eigen::last, 2)) = xmat_; - fullmat(Eigen::all, Eigen::seq(1, Eigen::last, 2)) = zmat_; + MatrixXb fullmat = MatrixXb::Zero(get_n_rows(), 2 * get_n_qubits()); + fullmat(Eigen::all, Eigen::seq(0, Eigen::last, 2)) = xmat; + fullmat(Eigen::all, Eigen::seq(1, Eigen::last, 2)) = zmat; std::vector> row_ops = gaussian_elimination_row_ops(fullmat); for (const std::pair &op : row_ops) { @@ -411,7 +399,7 @@ void SymplecticTableau::row_mult( Complex phase, MatrixXb::RowXpr &xw, MatrixXb::RowXpr &zw, bool &pw) { if (pa) phase *= -1; if (pb) phase *= -1; - for (unsigned i = 0; i < n_qubits_; i++) { + for (unsigned i = 0; i < get_n_qubits(); i++) { std::pair res = BoolPauli::mult_lut.at({{xa(i), za(i)}, {xb(i), zb(i)}}); xw(i) = res.first.x; @@ -424,18 +412,18 @@ void SymplecticTableau::row_mult( void SymplecticTableau::col_mult( const MatrixXb::ColXpr &a, const MatrixXb::ColXpr &b, bool flip, MatrixXb::ColXpr &w, VectorXb &pw) { - for (unsigned i = 0; i < n_rows_; i++) { + for (unsigned i = 0; i < get_n_rows(); i++) { pw(i) = pw(i) ^ (a(i) && (b(i) ^ flip)); w(i) = a(i) ^ b(i); } } void to_json(nlohmann::json &j, const SymplecticTableau &tab) { - j["nrows"] = tab.n_rows_; - j["nqubits"] = tab.n_qubits_; - j["xmat"] = tab.xmat_; - j["zmat"] = tab.zmat_; - j["phase"] = tab.phase_; + j["nrows"] = tab.get_n_rows(); + j["nqubits"] = tab.get_n_qubits(); + j["xmat"] = tab.xmat; + j["zmat"] = tab.zmat; + j["phase"] = tab.phase; } void from_json(const nlohmann::json &j, SymplecticTableau &tab) { diff --git a/tket/src/Clifford/UnitaryTableau.cpp b/tket/src/Clifford/UnitaryTableau.cpp index 3de53c2777..e73a1d1203 100644 --- a/tket/src/Clifford/UnitaryTableau.cpp +++ b/tket/src/Clifford/UnitaryTableau.cpp @@ -155,6 +155,16 @@ void UnitaryTableau::apply_S_at_end(const Qubit& qb) { tab_.apply_S(uqb); } +void UnitaryTableau::apply_Z_at_front(const Qubit& qb) { + unsigned uqb = qubits_.left.at(qb); + tab_.phase(uqb) = !tab_.phase(uqb); +} + +void UnitaryTableau::apply_Z_at_end(const Qubit& qb) { + unsigned uqb = qubits_.left.at(qb); + tab_.apply_Z(uqb); +} + void UnitaryTableau::apply_V_at_front(const Qubit& qb) { unsigned uqb = qubits_.left.at(qb); tab_.row_mult(uqb, uqb + qubits_.size(), -i_); @@ -165,6 +175,31 @@ void UnitaryTableau::apply_V_at_end(const Qubit& qb) { tab_.apply_V(uqb); } +void UnitaryTableau::apply_X_at_front(const Qubit& qb) { + unsigned uqb = qubits_.left.at(qb); + tab_.phase(uqb + qubits_.size()) = !tab_.phase(uqb + qubits_.size()); +} + +void UnitaryTableau::apply_X_at_end(const Qubit& qb) { + unsigned uqb = qubits_.left.at(qb); + tab_.apply_X(uqb); +} + +void UnitaryTableau::apply_H_at_front(const Qubit& qb) { + unsigned uqb = qubits_.left.at(qb); + unsigned n_qubits = qubits_.size(); + bool temp = tab_.phase(uqb); + tab_.phase(uqb) = tab_.phase(uqb + n_qubits); + tab_.phase(uqb + n_qubits) = temp; + tab_.xmat.row(uqb).swap(tab_.xmat.row(uqb + n_qubits)); + tab_.zmat.row(uqb).swap(tab_.zmat.row(uqb + n_qubits)); +} + +void UnitaryTableau::apply_H_at_end(const Qubit& qb) { + unsigned uqb = qubits_.left.at(qb); + tab_.apply_H(uqb); +} + void UnitaryTableau::apply_CX_at_front( const Qubit& control, const Qubit& target) { unsigned uc = qubits_.left.at(control); @@ -184,20 +219,16 @@ void UnitaryTableau::apply_gate_at_front( OpType type, const qubit_vector_t& qbs) { switch (type) { case OpType::Z: { - apply_S_at_front(qbs.at(0)); - apply_S_at_front(qbs.at(0)); + apply_Z_at_front(qbs.at(0)); break; } case OpType::X: { - apply_V_at_front(qbs.at(0)); - apply_V_at_front(qbs.at(0)); + apply_X_at_front(qbs.at(0)); break; } case OpType::Y: { - apply_S_at_front(qbs.at(0)); - apply_S_at_front(qbs.at(0)); - apply_V_at_front(qbs.at(0)); - apply_V_at_front(qbs.at(0)); + apply_Z_at_front(qbs.at(0)); + apply_X_at_front(qbs.at(0)); break; } case OpType::S: { @@ -206,24 +237,22 @@ void UnitaryTableau::apply_gate_at_front( } case OpType::Sdg: { apply_S_at_front(qbs.at(0)); - apply_S_at_front(qbs.at(0)); - apply_S_at_front(qbs.at(0)); + apply_Z_at_front(qbs.at(0)); break; } - case OpType::V: { + case OpType::V: + case OpType::SX: { apply_V_at_front(qbs.at(0)); break; } - case OpType::Vdg: { - apply_V_at_front(qbs.at(0)); - apply_V_at_front(qbs.at(0)); + case OpType::Vdg: + case OpType::SXdg: { apply_V_at_front(qbs.at(0)); + apply_X_at_front(qbs.at(0)); break; } case OpType::H: { - apply_S_at_front(qbs.at(0)); - apply_V_at_front(qbs.at(0)); - apply_S_at_front(qbs.at(0)); + apply_H_at_front(qbs.at(0)); break; } case OpType::CX: { @@ -234,18 +263,41 @@ void UnitaryTableau::apply_gate_at_front( apply_S_at_front(qbs.at(1)); apply_CX_at_front(qbs.at(0), qbs.at(1)); apply_S_at_front(qbs.at(1)); - apply_S_at_front(qbs.at(1)); - apply_S_at_front(qbs.at(1)); + apply_Z_at_front(qbs.at(1)); break; } case OpType::CZ: { - apply_S_at_front(qbs.at(1)); + apply_H_at_front(qbs.at(1)); + apply_CX_at_front(qbs.at(0), qbs.at(1)); + apply_H_at_front(qbs.at(1)); + break; + } + case OpType::ZZMax: { + apply_H_at_front(qbs.at(1)); + apply_S_at_front(qbs.at(0)); apply_V_at_front(qbs.at(1)); - apply_S_at_front(qbs.at(1)); apply_CX_at_front(qbs.at(0), qbs.at(1)); - apply_S_at_front(qbs.at(1)); + apply_H_at_front(qbs.at(1)); + break; + } + case OpType::ECR: { + apply_CX_at_front(qbs.at(0), qbs.at(1)); + apply_X_at_front(qbs.at(0)); + apply_S_at_front(qbs.at(0)); apply_V_at_front(qbs.at(1)); + apply_X_at_front(qbs.at(1)); + break; + } + case OpType::ISWAPMax: { + apply_V_at_front(qbs.at(0)); + apply_V_at_front(qbs.at(1)); + apply_CX_at_front(qbs.at(0), qbs.at(1)); + apply_V_at_front(qbs.at(0)); apply_S_at_front(qbs.at(1)); + apply_Z_at_front(qbs.at(1)); + apply_CX_at_front(qbs.at(0), qbs.at(1)); + apply_V_at_front(qbs.at(0)); + apply_V_at_front(qbs.at(1)); break; } case OpType::SWAP: { @@ -383,8 +435,8 @@ UnitaryTableau UnitaryTableau::dagger() const { for (unsigned j = 0; j < nqb; ++j) { // Take effect of some input on some output and invert auto inv_cell = invert_cell_map().at( - {BoolPauli{tab_.xmat_(i, j), tab_.zmat_(i, j)}, - BoolPauli{tab_.xmat_(i + nqb, j), tab_.zmat_(i + nqb, j)}}); + {BoolPauli{tab_.xmat(i, j), tab_.zmat(i, j)}, + BoolPauli{tab_.xmat(i + nqb, j), tab_.zmat(i + nqb, j)}}); // Transpose tableau and fill in cell dxx(j, i) = inv_cell.first.x; dxz(j, i) = inv_cell.first.z; @@ -399,9 +451,9 @@ UnitaryTableau UnitaryTableau::dagger() const { // Correct phases for (unsigned i = 0; i < nqb; ++i) { SpPauliStabiliser xr = dag.get_xrow(qubits_.right.at(i)); - dag.tab_.phase_(i) = get_row_product(xr).is_real_negative(); + dag.tab_.phase(i) = get_row_product(xr).is_real_negative(); SpPauliStabiliser zr = dag.get_zrow(qubits_.right.at(i)); - dag.tab_.phase_(i + nqb) = get_row_product(zr).is_real_negative(); + dag.tab_.phase(i + nqb) = get_row_product(zr).is_real_negative(); } return dag; @@ -422,14 +474,14 @@ std::ostream& operator<<(std::ostream& os, const UnitaryTableau& tab) { unsigned nqs = tab.qubits_.size(); for (unsigned i = 0; i < nqs; ++i) { Qubit qi = tab.qubits_.right.at(i); - os << "X@" << qi.repr() << "\t->\t" << tab.tab_.xmat_.row(i) << " " - << tab.tab_.zmat_.row(i) << " " << tab.tab_.phase_(i) << std::endl; + os << "X@" << qi.repr() << "\t->\t" << tab.tab_.xmat.row(i) << " " + << tab.tab_.zmat.row(i) << " " << tab.tab_.phase(i) << std::endl; } os << "--" << std::endl; for (unsigned i = 0; i < nqs; ++i) { Qubit qi = tab.qubits_.right.at(i); - os << "Z@" << qi.repr() << "\t->\t" << tab.tab_.xmat_.row(i + nqs) << " " - << tab.tab_.zmat_.row(i + nqs) << " " << tab.tab_.phase_(i + nqs) + os << "Z@" << qi.repr() << "\t->\t" << tab.tab_.xmat.row(i + nqs) << " " + << tab.tab_.zmat.row(i + nqs) << " " << tab.tab_.phase(i + nqs) << std::endl; } return os; @@ -446,13 +498,13 @@ bool UnitaryTableau::operator==(const UnitaryTableau& other) const { for (unsigned j = 0; j < nq; ++j) { Qubit qj = qubits_.right.at(j); unsigned oj = other.qubits_.left.at(qj); - if (tab_.xmat_(i, j) != other.tab_.xmat_(oi, oj)) return false; - if (tab_.zmat_(i, j) != other.tab_.zmat_(oi, oj)) return false; - if (tab_.xmat_(i + nq, j) != other.tab_.xmat_(oi + nq, oj)) return false; - if (tab_.zmat_(i + nq, j) != other.tab_.zmat_(oi + nq, oj)) return false; + if (tab_.xmat(i, j) != other.tab_.xmat(oi, oj)) return false; + if (tab_.zmat(i, j) != other.tab_.zmat(oi, oj)) return false; + if (tab_.xmat(i + nq, j) != other.tab_.xmat(oi + nq, oj)) return false; + if (tab_.zmat(i + nq, j) != other.tab_.zmat(oi + nq, oj)) return false; } - if (tab_.phase_(i) != other.tab_.phase_(oi)) return false; - if (tab_.phase_(i + nq) != other.tab_.phase_(oi + nq)) return false; + if (tab_.phase(i) != other.tab_.phase(oi)) return false; + if (tab_.phase(i + nq) != other.tab_.phase(oi + nq)) return false; } return true; @@ -529,6 +581,14 @@ void UnitaryRevTableau::apply_S_at_end(const Qubit& qb) { tab_.apply_pauli_at_front(SpPauliStabiliser(qb, Pauli::Z), 3); } +void UnitaryRevTableau::apply_Z_at_front(const Qubit& qb) { + tab_.apply_Z_at_end(qb); +} + +void UnitaryRevTableau::apply_Z_at_end(const Qubit& qb) { + tab_.apply_Z_at_front(qb); +} + void UnitaryRevTableau::apply_V_at_front(const Qubit& qb) { tab_.apply_pauli_at_end(SpPauliStabiliser(qb, Pauli::X), 3); } @@ -537,6 +597,22 @@ void UnitaryRevTableau::apply_V_at_end(const Qubit& qb) { tab_.apply_pauli_at_front(SpPauliStabiliser(qb, Pauli::X), 3); } +void UnitaryRevTableau::apply_X_at_front(const Qubit& qb) { + tab_.apply_X_at_end(qb); +} + +void UnitaryRevTableau::apply_X_at_end(const Qubit& qb) { + tab_.apply_X_at_front(qb); +} + +void UnitaryRevTableau::apply_H_at_front(const Qubit& qb) { + tab_.apply_H_at_end(qb); +} + +void UnitaryRevTableau::apply_H_at_end(const Qubit& qb) { + tab_.apply_H_at_front(qb); +} + void UnitaryRevTableau::apply_CX_at_front( const Qubit& control, const Qubit& target) { tab_.apply_CX_at_end(control, target); @@ -549,14 +625,54 @@ void UnitaryRevTableau::apply_CX_at_end( void UnitaryRevTableau::apply_gate_at_front( OpType type, const qubit_vector_t& qbs) { - if (type != OpType::Phase) - tab_.apply_gate_at_end(get_op_ptr(type)->dagger()->get_type(), qbs); + // Handle types whose dagger is not an optype + switch (type) { + case OpType::ZZMax: { + tab_.apply_gate_at_end(OpType::ZZMax, qbs); + tab_.apply_gate_at_end(OpType::Z, {qbs.at(0)}); + tab_.apply_gate_at_end(OpType::Z, {qbs.at(1)}); + break; + } + case OpType::ISWAPMax: { + tab_.apply_gate_at_end(OpType::ISWAPMax, qbs); + tab_.apply_gate_at_end(OpType::Z, {qbs.at(0)}); + tab_.apply_gate_at_end(OpType::Z, {qbs.at(1)}); + break; + } + case OpType::Phase: { + break; + } + default: { + tab_.apply_gate_at_end(get_op_ptr(type)->dagger()->get_type(), qbs); + break; + } + } } void UnitaryRevTableau::apply_gate_at_end( OpType type, const qubit_vector_t& qbs) { - if (type != OpType::Phase) - tab_.apply_gate_at_front(get_op_ptr(type)->dagger()->get_type(), qbs); + // Handle types whose dagger is not an optype + switch (type) { + case OpType::ZZMax: { + tab_.apply_gate_at_front(OpType::ZZMax, qbs); + tab_.apply_gate_at_front(OpType::Z, {qbs.at(0)}); + tab_.apply_gate_at_front(OpType::Z, {qbs.at(1)}); + break; + } + case OpType::ISWAPMax: { + tab_.apply_gate_at_front(OpType::ISWAPMax, qbs); + tab_.apply_gate_at_front(OpType::Z, {qbs.at(0)}); + tab_.apply_gate_at_front(OpType::Z, {qbs.at(1)}); + break; + } + case OpType::Phase: { + break; + } + default: { + tab_.apply_gate_at_front(get_op_ptr(type)->dagger()->get_type(), qbs); + break; + } + } } void UnitaryRevTableau::apply_pauli_at_front( @@ -598,16 +714,16 @@ std::ostream& operator<<(std::ostream& os, const UnitaryRevTableau& tab) { unsigned nqs = tab.tab_.qubits_.size(); for (unsigned i = 0; i < nqs; ++i) { Qubit qi = tab.tab_.qubits_.right.at(i); - os << tab.tab_.tab_.xmat_.row(i) << " " << tab.tab_.tab_.zmat_.row(i) - << " " << tab.tab_.tab_.phase_(i) << "\t->\t" << "X@" << qi.repr() + os << tab.tab_.tab_.xmat.row(i) << " " << tab.tab_.tab_.zmat.row(i) + << " " << tab.tab_.tab_.phase(i) << "\t->\t" << "X@" << qi.repr() << std::endl; } os << "--" << std::endl; for (unsigned i = 0; i < nqs; ++i) { Qubit qi = tab.tab_.qubits_.right.at(i); - os << tab.tab_.tab_.xmat_.row(i + nqs) << " " - << tab.tab_.tab_.zmat_.row(i + nqs) << " " - << tab.tab_.tab_.phase_(i + nqs) << "\t->\t" << "Z@" << qi.repr() + os << tab.tab_.tab_.xmat.row(i + nqs) << " " + << tab.tab_.tab_.zmat.row(i + nqs) << " " + << tab.tab_.tab_.phase(i + nqs) << "\t->\t" << "Z@" << qi.repr() << std::endl; } return os; diff --git a/tket/src/Converters/ChoiMixTableauConverters.cpp b/tket/src/Converters/ChoiMixTableauConverters.cpp index 14bba5fcc2..624b0dc657 100644 --- a/tket/src/Converters/ChoiMixTableauConverters.cpp +++ b/tket/src/Converters/ChoiMixTableauConverters.cpp @@ -30,637 +30,732 @@ ChoiMixTableau circuit_to_cm_tableau(const Circuit& circ) { qubit_vector_t qbs = {args.begin(), args.end()}; tab.apply_gate(com.get_op_ptr()->get_type(), qbs); } + tab.rename_qubits( + circ.implicit_qubit_permutation(), + ChoiMixTableau::TableauSegment::Output); for (const Qubit& q : circ.discarded_qubits()) { tab.discard_qubit(q); } + tab.canonical_column_order(); + tab.gaussian_form(); return tab; } -std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { +struct ChoiMixBuilder { /** - * THE PLAN: - * We first identify and solve all post-selections by Gaussian elimination and - * diagonalisation of the input-only rows. This provides us with better - * guarantees about the form of the stabilizers generated by the remaining - * tableau - specifically that every possible stabilizer will involve at least - * some output portion, so out_qb will always be set in later sections of the - * synthesis. Diagonalisation of the input-only rows reduces them to just Z - * strings but not necessarily over a minimal number of qubits. This can be - * achieved by taking the Z matrix of these rows and performing row-wise - * Gaussian elimination. The leading 1s indicate which qubits we are isolating - * them onto and the rest gives the CXs required to reduce them to just the - * leading qubits. After all post-selections have been identified, we enter - * the main loop of handling all other inputs and reducing them one at a time - * to either an identity wire or Z-decoherence (OpType::Collapse) connected to - * an output, or to a discard. Let in_qb be this qubit to be solved. Pick a - * row containing X_in_qb (if one exists). Since it must contain some - * non-identity component in the output segment, we can pick one of these to - * be out_qb and apply unitary gates at the input and output to reduce the row - * to X_in_qb X_out_qb. We do the same with Z (if a row exists) to necessarily - * give Z_in_qb Z_out_qb by commutativity properties, or we pick out_qb here - * if no X row was found. If we had both an X row and a Z row, we have reduced - * it to an identity wire just fine. If we have only one of them, e.g. X_in_qb - * X_out_qb, we wish to make it the only row with X on either in_qb or out_qb - * to leave a decoherence channel. We note that any other row containing - * X_out_qb must have some other P_out2, since their combination cannot leave - * an empty output segment. So we can apply a unitary gate to eliminate the - * X_out_qb on the other row. By doing output-first gaussian elimination on - * the output sub-tableau ignoring the target row and X_out_qb column, for - * each such row with X_out_qb there is some other output column P_out2 for - * which it is the unique entry, so we can be sure that applying the unitary - * gate does not add X_out_qb onto other rows. Having this strategy available - * to make it the unique row with X_out_qb still allows us to make it the - * unique row with X_in_qb by row combinations. Once all rows with inputs have - * been eliminated, any unsolved inputs must have been discarded and the - * remaining tableau is an inverse diagonalisation tableau (only outputs). + * We will consider applying gates to either side of the tableau to reduce + * qubits down to one of a few simple states (identity, collapse, zero + * initialise, mixed initialised, post-selected, or discarded), allowing us to + * remove that qubit and continue until the tableau contains no qubits left. + * This gradually builds up a set of operations both before and after the + * working tableau. We should ask for the following combination of temporary + * states to compose to form a channel equivalent to that described by the + * input tableau: + * - in_circ: a unitary circuit (without implicit permutations) + * - post_selected: a set of post-selection actions to apply to the outputs of + * in_circ + * - discarded: a set of discard actions to apply to the outputs of in_circ + * - collapsed: a set of collapse actions to apply to the outputs of in_circ + * (i.e. decoherence in the Z basis) + * - tab: the remaining tableau still to be solved. Acting as an identity on + * any qubits not contained within tab + * - in_out_permutation: a permutation of the qubits, read as a map from the + * input qubit name to the output qubit it is sent to + * - zero_initialised: a set of initialisations of fresh output qubits + * (in_out_permutation may join these qubits onto input qubits that have been + * post-selected or discarded to reuse qubits) + * - mix_initialised: a set of initialisations of output qubits into + * maximally-mixed states (in_out_permutation may similarly join these onto + * input qubits no longer in use) + * - out_circ_tp: the transpose of a unitary circuit (without implicit + * permuations); we store this as the transpose so we can build it up in + * reverse */ + Circuit in_circ; + std::set post_selected; + std::set discarded; + std::set collapsed; + ChoiMixTableau tab; + boost::bimap in_out_permutation; + std::set zero_initialised; + std::set mix_initialised; + Circuit out_circ_tp; + + // The CXConfigType preferred when invoking diagonalisation techniques + CXConfigType cx_config; + + // Additional qubit names (distinct from qubits already on the respective + // segment of the tableau) than can be used for zero initialisations and + // post-selections when synthesising a unitary extension + qubit_vector_t unitary_init_names; + qubit_vector_t unitary_post_names; + + // Initialises the builder with a tableau + explicit ChoiMixBuilder(const ChoiMixTableau& tab, CXConfigType cx_config); + // For synthesis of a unitary extension, initialises the builder with a + // tableau and some additional qubit names which the resulting circuit may + // optionally use, representing zero-initialised or post-selected qubits. + // These are the qubits on which we can freely add Zs to the rows of the given + // tableau to guarantee a unitary extension; none of these names should appear + // in tab + explicit ChoiMixBuilder( + const ChoiMixTableau& tab, CXConfigType cx_config, + const qubit_vector_t& init_names, const qubit_vector_t& post_names); + + // Debug method: applies all staged operations back onto tab to provide the + // tableau that the synthesis result is currently aiming towards. In exact + // synthesis, this should remain invariant during synthesis. For synthesis of + // a unitary extension, this should at least span the rows of the original + // tableau up to additional Zs on spare input and output qubits. + ChoiMixTableau realised_tableau() const; - // Operate on a copy of the tableau to track the remainder as we extract gates - ChoiMixTableau tab(t); + /** + * STAGES OF SYNTHESIS + */ - // Canonicalise tableau and perform output-first gaussian elimination to - // isolate post-selected subspace in lower rows - tab.canonical_column_order(ChoiMixTableau::TableauSegment::Output); - tab.gaussian_form(); + // Match up pairs of generators that anti-commute in the input segment but + // commute with all others; such pairs of rows reduce to an identity wire + // between a pair of qubits; we solve this by pairwise Pauli reduction methods + void solve_id_subspace(); + // After removing the identity subspace, all remaining rows mutually commute + // within each tableau segment; diagonalise each segment individually + void diagonalise_segments(); + // Solve the post-selected subspace which has already been diagonalised + void solve_postselected_subspace(); + // Solve the zero-initialised subspace which has already been diagonalised + void solve_initialised_subspace(); + // All remaining rows are in the collapsed subspace (each row is the unique + // stabilizer passing through some Collapse gate); solve it + void solve_collapsed_subspace(); + + // Simplifies the tableau by removing qubits on which all rows have I; such + // qubits are either discarded inputs or mixed-initialised outputs + void remove_unused_qubits(); + // For synthesis of a unitary extension, match up qubits from + // post-selected/zero-initialised with unitary_post_names/unitary_init_names + // and add them to in_out_permutation + void assign_init_post_names(); + // Fill out in_out_permutation to map all qubits; this typically takes the + // form of a standard qubit reuse pattern (e.g. discard and reinitialise) + void assign_remaining_names(); + // Once tab has been completely reduced to no rows and no qubits, compose the + // staged operations to build the output circuit and return the renaming map + // from output names of original tableau to the qubits of the returned circuit + // they are mapped to + std::pair output_circuit(); + std::pair unitary_output_circuit(); +}; + +std::pair cm_tableau_to_exact_circuit( + const ChoiMixTableau& tab, CXConfigType cx_config) { + ChoiMixBuilder builder(tab, cx_config); + builder.remove_unused_qubits(); + builder.solve_id_subspace(); + builder.diagonalise_segments(); + builder.solve_postselected_subspace(); + builder.solve_initialised_subspace(); + builder.solve_collapsed_subspace(); + builder.remove_unused_qubits(); + builder.assign_remaining_names(); + return builder.output_circuit(); +} - // Set up circuits for extracting gates into (in_circ is set up after - // diagonalising the post-selected subspace) - qubit_vector_t input_qubits, output_qubits; +std::pair cm_tableau_to_unitary_extension_circuit( + const ChoiMixTableau& tab, const std::vector& init_names, + const std::vector& post_names, CXConfigType cx_config) { + ChoiMixBuilder builder(tab, cx_config, init_names, post_names); + builder.remove_unused_qubits(); + builder.solve_id_subspace(); + builder.diagonalise_segments(); + builder.solve_postselected_subspace(); + builder.solve_initialised_subspace(); + builder.solve_collapsed_subspace(); + builder.remove_unused_qubits(); + builder.assign_init_post_names(); + builder.assign_remaining_names(); + return builder.unitary_output_circuit(); +} + +ChoiMixBuilder::ChoiMixBuilder(const ChoiMixTableau& t, CXConfigType cx) + : ChoiMixBuilder(t, cx, {}, {}) {} + +ChoiMixBuilder::ChoiMixBuilder( + const ChoiMixTableau& t, CXConfigType cx, const qubit_vector_t& inits, + const qubit_vector_t& posts) + : in_circ(), + post_selected(), + discarded(), + collapsed(), + tab(t), + in_out_permutation(), + zero_initialised(), + mix_initialised(), + out_circ_tp(), + cx_config(cx), + unitary_init_names(inits), + unitary_post_names(posts) { for (unsigned i = 0; i < tab.get_n_boundaries(); ++i) { ChoiMixTableau::col_key_t key = tab.col_index_.right.at(i); if (key.second == ChoiMixTableau::TableauSegment::Input) - input_qubits.push_back(key.first); + in_circ.add_qubit(key.first); else - output_qubits.push_back(key.first); + out_circ_tp.add_qubit(key.first); } - Circuit out_circ_tp(output_qubits, {}); - unit_map_t join_permutation; - std::set post_selected; - std::map> in_x_row; - std::map> in_z_row; - boost::bimap matched_qubits; - - // Call diagonalisation methods to diagonalise post-selected subspace - std::list to_diag; - for (unsigned r = tab.get_n_rows(); r > 0;) { - --r; - ChoiMixTableau::row_tensor_t rten = tab.get_row(r); - if (rten.second.size() != 0) { - // Reached the rows with non-empty output segment - break; - } - // Else, we add the row to the subspace - to_diag.push_back(rten.first); + for (const Qubit& init_q : unitary_init_names) { + if (tab.col_index_.left.find(ChoiMixTableau::col_key_t{ + init_q, ChoiMixTableau::TableauSegment::Input}) != + tab.col_index_.left.end()) + throw std::logic_error( + "Free qubit name for initialisation conflicts with existing live " + "input of ChoiMixTableau"); } - unsigned post_selected_size = to_diag.size(); - std::set diag_ins{input_qubits.begin(), input_qubits.end()}; - Circuit in_circ = mutual_diagonalise(to_diag, diag_ins, CXConfigType::Tree); - // Extract the dagger of each gate in order from tab - for (const Command& com : in_circ) { - auto args = com.get_args(); - qubit_vector_t qbs = {args.begin(), args.end()}; - tab.apply_gate( - com.get_op_ptr()->dagger()->get_type(), qbs, - ChoiMixTableau::TableauSegment::Input); + for (const Qubit& post_q : unitary_post_names) { + if (tab.col_index_.left.find(ChoiMixTableau::col_key_t{ + post_q, ChoiMixTableau::TableauSegment::Output}) != + tab.col_index_.left.end()) + throw std::logic_error( + "Free qubit name for post-selection conflicts with existing live " + "output of ChoiMixTableau"); } +} - // Diagonalised rows should still be at the bottom of the tableau - reduce - // them to a minimal set of qubits for post-selection by first reducing to - // upper echelon form - std::vector> row_ops = - gaussian_elimination_row_ops( - tab.tab_.zmat_.bottomRows(post_selected_size)); - for (const std::pair& op : row_ops) { - tab.tab_.row_mult(op.first, op.second); - } - // Obtain CX instructions as column operations - std::vector> col_ops = - gaussian_elimination_col_ops( - tab.tab_.zmat_.bottomRows(post_selected_size)); - // These gates will also swap qubits to isolate the post-selections on the - // first few qubits - this is fine as can be cleaned up later with peephole - // optimisations - for (const std::pair& op : col_ops) { - tab.tab_.apply_CX(op.first, op.second); - ChoiMixTableau::col_key_t ctrl = tab.col_index_.right.at(op.first); - ChoiMixTableau::col_key_t trgt = tab.col_index_.right.at(op.second); - in_circ.add_op(OpType::CX, {ctrl.first, trgt.first}); +// DEBUG METHOD: Ignore this for coverage checks +// GCOVR_EXCL_START +ChoiMixTableau ChoiMixBuilder::realised_tableau() const { + ChoiMixTableau in_tab = circuit_to_cm_tableau(in_circ); + for (const Qubit& q : post_selected) + in_tab.post_select(q, ChoiMixTableau::TableauSegment::Output); + for (const Qubit& q : discarded) + in_tab.discard_qubit(q, ChoiMixTableau::TableauSegment::Output); + for (const Qubit& q : collapsed) + in_tab.collapse_qubit(q, ChoiMixTableau::TableauSegment::Output); + ChoiMixTableau out_tab = circuit_to_cm_tableau(out_circ_tp.transpose()); + for (const Qubit& q : zero_initialised) + out_tab.post_select(q, ChoiMixTableau::TableauSegment::Input); + for (const Qubit& q : mix_initialised) + out_tab.discard_qubit(q, ChoiMixTableau::TableauSegment::Input); + qubit_map_t out_in_permutation{}; + using perm_entry = boost::bimap::left_const_reference; + BOOST_FOREACH (perm_entry entry, in_out_permutation.left) { + out_in_permutation.insert({entry.second, entry.first}); } + out_tab.rename_qubits( + out_in_permutation, ChoiMixTableau::TableauSegment::Output); + return ChoiMixTableau::compose(ChoiMixTableau::compose(in_tab, tab), out_tab); +} +// GCOVR_EXCL_STOP - // Post-select rows - // TODO Post-selection op is not yet available in tket - replace this once - // implemented; an implementation hint is provided below - if (post_selected_size != 0) - throw std::logic_error( - "Not yet implemented: post-selection required during ChoiMixTableau " - "synthesis"); - // for (unsigned r = 0; r < post_selected_size; ++r) { - // ChoiMixTableau::row_tensor_t row = tab.get_row(tab.get_n_rows() - 1); - // if (row.second.string.map.size() != 0 || row.first.string.map.size() != 1 - // || row.first.string.map.begin()->second != Pauli::Z) throw - // std::logic_error("Unexpected error during post-selection identification - // in ChoiMixTableau synthesis"); Qubit post_selected_qb = - // row.first.string.map.begin()->first; if (row.second.coeff == -1.) - // in_circ.add_op(OpType::X, {post_selected_qb}); - // tab.remove_row(tab.get_n_rows() - 1); - // post_selected.insert(post_selected_qb); - // throw std::logic_error("Not yet implemented: post-selection required - // during ChoiMixTableau synthesis"); - // } - +void ChoiMixBuilder::solve_id_subspace() { // Input-first gaussian elimination to solve input-sides of remaining rows tab.canonical_column_order(ChoiMixTableau::TableauSegment::Input); tab.gaussian_form(); - // Iterate through remaining inputs and reduce output portion to a single - // qubit - for (const Qubit& in_qb : input_qubits) { - // Skip post-selected qubits - if (post_selected.find(in_qb) != post_selected.end()) continue; - - unsigned col = tab.col_index_.left.at(ChoiMixTableau::col_key_t{ - in_qb, ChoiMixTableau::TableauSegment::Input}); - std::optional out_qb = std::nullopt; + std::set solved_rows; + std::set solved_ins, solved_outs; + for (unsigned r = 0; r < tab.get_n_rows(); ++r) { + if (solved_rows.find(r) != solved_rows.end()) continue; - // Find the row with X_in_qb (if one exists) - std::optional x_row = std::nullopt; - for (unsigned r = 0; r < tab.get_n_rows(); ++r) { - if (tab.tab_.xmat_(r, col)) { - x_row = r; - break; - } + // Look for a row which anticommutes with row r over the inputs + std::list xcols, zcols; + for (unsigned c = 0; c < tab.get_n_inputs(); ++c) { + if (tab.tab_.xmat(r, c)) xcols.push_back(c); + if (tab.tab_.zmat(r, c)) zcols.push_back(c); } - - if (x_row) { - // A possible optimisation could involve row multiplications to reduce the - // Hamming weight of the row before applying gates, but minimising this - // would solve minimum weight/distance of a binary linear code whose - // decision problem is NP-complete (shown by Vardy, "The intractability of - // computing the minimum distance of a code", 1997). Just settle on using - // the first row for now, reducing the input and output to a single qubit - ChoiMixTableau::row_tensor_t row_paulis = tab.get_row(*x_row); - for (const std::pair& p : row_paulis.second.string) { - if (matched_qubits.right.find(p.first) == matched_qubits.right.end()) { - out_qb = p.first; - matched_qubits.insert({in_qb, *out_qb}); - break; - } + for (unsigned r2 = r + 1; r2 < tab.get_n_rows(); ++r2) { + if (solved_rows.find(r2) != solved_rows.end()) continue; + bool anti = false; + for (const unsigned c : xcols) anti ^= tab.tab_.zmat(r2, c); + for (const unsigned c : zcols) anti ^= tab.tab_.xmat(r2, c); + if (!anti) continue; + + // Found a candidate pair of rows. Because of the Gaussian elimination, it + // is more likely that the first mismatching qubit is X for r and Z for + // r2, so favour reducing r2 to Z and r to X + ChoiMixTableau::row_tensor_t row_r = tab.get_row(r); + ChoiMixTableau::row_tensor_t row_r2 = tab.get_row(r2); + std::pair in_diag_circ = + reduce_anticommuting_paulis_to_z_x( + row_r2.first, row_r.first, cx_config); + in_circ.append(in_diag_circ.first); + for (const Command& com : in_diag_circ.first) { + auto args = com.get_args(); + qubit_vector_t qbs = {args.begin(), args.end()}; + tab.apply_gate( + com.get_op_ptr()->dagger()->get_type(), qbs, + ChoiMixTableau::TableauSegment::Input); } - // Reduce input string to just X_in_qb - if (row_paulis.first.get(in_qb) == Pauli::Y) { - // If it is a Y, extract an Sdg gate so the Pauli is exactly X - in_circ.add_op(OpType::Sdg, {in_qb}); - tab.apply_S(in_qb, ChoiMixTableau::TableauSegment::Input); - } - for (const std::pair& qbp : row_paulis.first.string) { - if (qbp.first == in_qb) continue; - // Extract an entangling gate to eliminate the qubit - switch (qbp.second) { - case Pauli::X: { - in_circ.add_op(OpType::CX, {in_qb, qbp.first}); - tab.apply_CX( - in_qb, qbp.first, ChoiMixTableau::TableauSegment::Input); - break; - } - case Pauli::Y: { - in_circ.add_op(OpType::CY, {in_qb, qbp.first}); - tab.apply_gate( - OpType::CY, {in_qb, qbp.first}, - ChoiMixTableau::TableauSegment::Input); - break; - } - case Pauli::Z: { - in_circ.add_op(OpType::CZ, {in_qb, qbp.first}); - tab.apply_gate( - OpType::CZ, {in_qb, qbp.first}, - ChoiMixTableau::TableauSegment::Input); - break; - } - default: { - break; - } - } + // Since the full rows must commute but they anticommute over the inputs, + // they must also anticommute over the outputs; we similarly reduce these + // down to Z and X + std::pair out_diag_circ_dag = + reduce_anticommuting_paulis_to_z_x( + row_r2.second, row_r.second, cx_config); + out_circ_tp.append(out_diag_circ_dag.first.dagger().transpose()); + for (const Command& com : out_diag_circ_dag.first) { + auto args = com.get_args(); + qubit_vector_t qbs = {args.begin(), args.end()}; + tab.apply_gate( + com.get_op_ptr()->get_type(), qbs, + ChoiMixTableau::TableauSegment::Output); } - // And then the same for X_out_qb - if (row_paulis.second.get(*out_qb) == Pauli::Y) { - // If it is a Y, extract an Sdg gate so the Pauli is exactly X - out_circ_tp.add_op(OpType::Sdg, {*out_qb}); - tab.apply_S(*out_qb, ChoiMixTableau::TableauSegment::Output); - } else if (row_paulis.second.get(*out_qb) == Pauli::Z) { - // If it is a Z, extract an Vdg and Sdg gate so the Pauli is exactly X - out_circ_tp.add_op(OpType::Vdg, {*out_qb}); - out_circ_tp.add_op(OpType::Sdg, {*out_qb}); - tab.apply_V(*out_qb, ChoiMixTableau::TableauSegment::Output); - tab.apply_S(*out_qb, ChoiMixTableau::TableauSegment::Output); + // Check that rows have been successfully reduced + row_r = tab.get_row(r); + row_r2 = tab.get_row(r2); + if (row_r.first.size() != 1 || + row_r.first.string.begin()->second != Pauli::X || + row_r.second.size() != 1 || + row_r.second.string.begin()->second != Pauli::X || + row_r2.first.size() != 1 || + row_r2.first.string.begin()->second != Pauli::Z || + row_r2.second.size() != 1 || + row_r2.second.string.begin()->second != Pauli::Z) + throw std::logic_error( + "Unexpected error during identity reduction in ChoiMixTableau " + "synthesis"); + // Solve phases + if (row_r.second.is_real_negative()) { + in_circ.add_op(OpType::Z, {in_diag_circ.second}); + tab.apply_gate( + OpType::Z, {in_diag_circ.second}, + ChoiMixTableau::TableauSegment::Input); } - for (const std::pair& qbp : - row_paulis.second.string) { - if (qbp.first == *out_qb) continue; - // Extract an entangling gate to eliminate the qubit - switch (qbp.second) { - case Pauli::X: { - out_circ_tp.add_op(OpType::CX, {*out_qb, qbp.first}); - tab.apply_CX( - *out_qb, qbp.first, ChoiMixTableau::TableauSegment::Output); - break; - } - case Pauli::Y: { - // CY does not have a transpose OpType defined so decompose - out_circ_tp.add_op(OpType::S, {qbp.first}); - out_circ_tp.add_op(OpType::CX, {*out_qb, qbp.first}); - out_circ_tp.add_op(OpType::Sdg, {qbp.first}); - tab.apply_gate( - OpType::CY, {*out_qb, qbp.first}, - ChoiMixTableau::TableauSegment::Output); - break; - } - case Pauli::Z: { - out_circ_tp.add_op(OpType::CZ, {*out_qb, qbp.first}); - tab.apply_gate( - OpType::CZ, {*out_qb, qbp.first}, - ChoiMixTableau::TableauSegment::Output); - break; - } - default: { - break; - } - } + if (row_r2.second.is_real_negative()) { + in_circ.add_op(OpType::X, {in_diag_circ.second}); + tab.apply_gate( + OpType::X, {in_diag_circ.second}, + ChoiMixTableau::TableauSegment::Input); } - } - - // Find the row with Z_in_qb (if one exists) - std::optional z_row = std::nullopt; - for (unsigned r = 0; r < tab.get_n_rows(); ++r) { - if (tab.tab_.zmat_(r, col)) { - z_row = r; - break; + // Connect in permutation + in_out_permutation.insert( + {in_diag_circ.second, out_diag_circ_dag.second}); + solved_rows.insert(r); + solved_rows.insert(r2); + + // Remove these solved qubits from other rows; by commutation of rows, a + // row contains Z@in_diag_circ.second iff it contains + // Z@out_diag_circ_dag.second and similarly for X + unsigned in_c = tab.col_index_.left.at(ChoiMixTableau::col_key_t{ + in_diag_circ.second, ChoiMixTableau::TableauSegment::Input}); + for (unsigned r3 = 0; r3 < tab.get_n_rows(); ++r3) { + if (r3 != r && tab.tab_.xmat(r3, in_c)) tab.tab_.row_mult(r, r3); + if (r3 != r2 && tab.tab_.zmat(r3, in_c)) tab.tab_.row_mult(r2, r3); } - } - if (z_row) { - // If both an X and Z row exist, then out_qb should have a - // value and the rows should have anticommuting Paulis on out_qb to - // preserve commutativity of rows - ChoiMixTableau::row_tensor_t row_paulis = tab.get_row(*z_row); - if (!x_row) { - for (const std::pair& p : - row_paulis.second.string) { - if (matched_qubits.right.find(p.first) == - matched_qubits.right.end()) { - out_qb = p.first; - matched_qubits.insert({in_qb, *out_qb}); - break; - } - } - } - - // Reduce input string to just Z_in_qb. - // No need to consider different paulis on in_qb: if we had X or Y, this - // row would have been identified as x_row instead of Z row, and if - // another row was already chosen as x_row then canonical gaussian form - // would imply all other rows do not contain X_in_qb or Y_in_qb - for (const std::pair& qbp : row_paulis.first.string) { - if (qbp.first == in_qb) continue; - // Extract an entangling gate to eliminate the qubit - switch (qbp.second) { - case Pauli::X: { - in_circ.add_op(OpType::H, {qbp.first}); - in_circ.add_op(OpType::CX, {qbp.first, in_qb}); - tab.apply_gate( - OpType::H, {qbp.first}, ChoiMixTableau::TableauSegment::Input); - tab.apply_CX( - qbp.first, in_qb, ChoiMixTableau::TableauSegment::Input); - break; - } - case Pauli::Y: { - in_circ.add_op(OpType::Vdg, {qbp.first}); - in_circ.add_op(OpType::CX, {qbp.first, in_qb}); - tab.apply_V(qbp.first, ChoiMixTableau::TableauSegment::Input); - tab.apply_CX( - qbp.first, in_qb, ChoiMixTableau::TableauSegment::Input); - break; - } - case Pauli::Z: { - in_circ.add_op(OpType::CX, {qbp.first, in_qb}); - tab.apply_CX( - qbp.first, in_qb, ChoiMixTableau::TableauSegment::Input); - break; - } - default: { - break; - } - } - } - - // And then reduce output string to just Z_out_qb - if (row_paulis.second.get(*out_qb) == Pauli::Y) { - // If it is a Y, extract a Vdg gate so the Pauli is exactly Z - out_circ_tp.add_op(OpType::Vdg, {*out_qb}); - tab.apply_V(*out_qb, ChoiMixTableau::TableauSegment::Output); - } else if (row_paulis.second.get(*out_qb) == Pauli::X) { - // If it is an X, extract an Sdg and Vdg gate so the Pauli is exactly Z - // We do not need to care about messing up the X row here since if we - // solved an X row then this row can't also have X by commutativity - out_circ_tp.add_op(OpType::Sdg, {*out_qb}); - out_circ_tp.add_op(OpType::Vdg, {*out_qb}); - tab.apply_S(*out_qb, ChoiMixTableau::TableauSegment::Output); - tab.apply_V(*out_qb, ChoiMixTableau::TableauSegment::Output); - } - for (const std::pair& qbp : - row_paulis.second.string) { - if (qbp.first == *out_qb) continue; - // Extract an entangling gate to eliminate the qubit - switch (qbp.second) { - case Pauli::X: { - out_circ_tp.add_op(OpType::H, {qbp.first}); - out_circ_tp.add_op(OpType::CX, {qbp.first, *out_qb}); - tab.apply_gate( - OpType::H, {qbp.first}, ChoiMixTableau::TableauSegment::Output); - tab.apply_CX( - qbp.first, *out_qb, ChoiMixTableau::TableauSegment::Output); - break; - } - case Pauli::Y: { - out_circ_tp.add_op(OpType::Vdg, {qbp.first}); - out_circ_tp.add_op(OpType::CX, {qbp.first, *out_qb}); - tab.apply_V(qbp.first, ChoiMixTableau::TableauSegment::Output); - tab.apply_CX( - qbp.first, *out_qb, ChoiMixTableau::TableauSegment::Output); - break; - } - case Pauli::Z: { - out_circ_tp.add_op(OpType::CX, {qbp.first, *out_qb}); - tab.apply_CX( - qbp.first, *out_qb, ChoiMixTableau::TableauSegment::Output); - break; - } - default: { - break; - } - } - } - } - - in_x_row.insert({in_qb, x_row}); - in_z_row.insert({in_qb, z_row}); - } - - // X and Z row are guaranteed to be the unique rows with X_in_qb and Z_in_qb. - // If the Z row exists, then all rows must commute with Z_in_qb Z_out_qb, so - // if any row has X_out_qb it must also have X_in_qb. Hence if both exist then - // the X row is also the unique row with X_out_qb, and by similar argument the - // Z row is the unique row with Z_out_qb. However, if only one row exists, - // this uniqueness may not hold but can be forced by applying some unitary - // gates to eliminate e.g. Z_out_qb from all other rows. We use a gaussian - // elimination subroutine to identify a combination of gates that won't add - // Z_out_qb onto other qubits. Since we have already removed them from all - // other rows containing input components, we only need to consider the - // output-only rows, which all exist at the bottom of the tableau. - unsigned out_stabs = 0; - while (out_stabs < tab.get_n_rows()) { - ChoiMixTableau::row_tensor_t rten = - tab.get_row(tab.get_n_rows() - 1 - out_stabs); - if (rten.first.size() != 0) { - // Reached the rows with non-empty input segment + solved_ins.insert({in_diag_circ.second}); + solved_outs.insert({out_diag_circ_dag.second}); break; } - ++out_stabs; - } - MatrixXb out_rows = MatrixXb::Zero(out_stabs, 2 * output_qubits.size()); - out_rows(Eigen::all, Eigen::seq(0, Eigen::last, 2)) = tab.tab_.xmat_.block( - tab.get_n_rows() - out_stabs, input_qubits.size(), out_stabs, - output_qubits.size()); - out_rows(Eigen::all, Eigen::seq(1, Eigen::last, 2)) = tab.tab_.zmat_.block( - tab.get_n_rows() - out_stabs, input_qubits.size(), out_stabs, - output_qubits.size()); - // Identify other qubits to apply gates to for removing the extra matched - // output terms - MatrixXb unmatched_outs = MatrixXb::Zero( - out_stabs, 2 * (output_qubits.size() - matched_qubits.size())); - std::vector> col_lookup; - for (unsigned out_col = 0; out_col < output_qubits.size(); ++out_col) { - unsigned tab_col = input_qubits.size() + out_col; - Qubit out_qb = tab.col_index_.right.at(tab_col).first; - if (matched_qubits.right.find(out_qb) != matched_qubits.right.end()) - continue; - unmatched_outs.col(col_lookup.size()) = out_rows.col(2 * out_col); - col_lookup.push_back({out_qb, Pauli::X}); - unmatched_outs.col(col_lookup.size()) = out_rows.col(2 * out_col + 1); - col_lookup.push_back({out_qb, Pauli::Z}); - } - row_ops = gaussian_elimination_row_ops(unmatched_outs); - for (const std::pair& op : row_ops) { - for (unsigned c = 0; c < unmatched_outs.cols(); ++c) { - unmatched_outs(op.second, c) = - unmatched_outs(op.second, c) ^ unmatched_outs(op.first, c); - } - tab.tab_.row_mult( - tab.get_n_rows() - out_stabs + op.first, - tab.get_n_rows() - out_stabs + op.second); - } - // Go through the output-only rows and remove terms from matched qubits - for (unsigned r = 0; r < out_stabs; ++r) { - unsigned leading_col = 0; - for (unsigned c = 0; c < unmatched_outs.cols(); ++c) { - if (unmatched_outs(r, c)) { - leading_col = c; - break; - } - } - std::pair alternate_qb = col_lookup.at(leading_col); - - if (alternate_qb.second != Pauli::X) { - // Make the alternate point of contact X so we only need one set of rule - // for eliminating Paulis - tab.apply_gate( - OpType::H, {alternate_qb.first}, - ChoiMixTableau::TableauSegment::Output); - out_circ_tp.add_op(OpType::H, {alternate_qb.first}); - } - - ChoiMixTableau::row_tensor_t row_paulis = - tab.get_row(tab.get_n_rows() - out_stabs + r); - - for (const std::pair& qbp : row_paulis.second.string) { - if (matched_qubits.right.find(qbp.first) == matched_qubits.right.end()) - continue; - // Alternate point is guaranteed to be unmatched, so always needs an - // entangling gate - switch (qbp.second) { - case Pauli::X: { - out_circ_tp.add_op( - OpType::CX, {alternate_qb.first, qbp.first}); - tab.apply_CX( - alternate_qb.first, qbp.first, - ChoiMixTableau::TableauSegment::Output); - break; - } - case Pauli::Z: { - out_circ_tp.add_op( - OpType::CZ, {alternate_qb.first, qbp.first}); - tab.apply_gate( - OpType::CZ, {alternate_qb.first, qbp.first}, - ChoiMixTableau::TableauSegment::Output); - break; - } - default: { - // Don't have to care about Y since any matched qubit has a row that - // is reduced to either X or Z and all other rows must commute with - // that - break; - } - } - } } - // Now that X_in_qb X_out_qb (or Zs) is the unique row for each of X_in_qb and - // X_out_qb, we can actually link up the qubit wires and remove the rows - for (const Qubit& in_qb : input_qubits) { - if (post_selected.find(in_qb) != post_selected.end()) continue; - - std::optional x_row = in_x_row.at(in_qb); - std::optional z_row = in_z_row.at(in_qb); - auto found = matched_qubits.left.find(in_qb); - std::optional out_qb = (found == matched_qubits.left.end()) - ? std::nullopt - : std::optional{found->second}; - // Handle phases and resolve qubit connections - if (x_row) { - if (z_row) { - // Hook up with an identity wire - if (tab.tab_.phase_(*z_row)) in_circ.add_op(OpType::X, {in_qb}); - if (tab.tab_.phase_(*x_row)) in_circ.add_op(OpType::Z, {in_qb}); - join_permutation.insert({*out_qb, in_qb}); - } else { - // Just an X row, so must be connected to out_qb via a decoherence - if (tab.tab_.phase_(*x_row)) in_circ.add_op(OpType::Z, {in_qb}); - in_circ.add_op(OpType::H, {in_qb}); - in_circ.add_op(OpType::Collapse, {in_qb}); - in_circ.add_op(OpType::H, {in_qb}); - join_permutation.insert({*out_qb, in_qb}); - } - } else { - if (z_row) { - // Just a Z row, so must be connected to out_qb via a decoherence - if (tab.tab_.phase_(*z_row)) in_circ.add_op(OpType::X, {in_qb}); - in_circ.add_op(OpType::Collapse, {in_qb}); - join_permutation.insert({*out_qb, in_qb}); - } else { - // No rows involving this input, so it is discarded - in_circ.qubit_discard(in_qb); + // Remove solved rows and qubits from tableau; since removing rows/columns + // replaces them with the row/column from the end, remove in reverse order + for (auto it = solved_rows.rbegin(); it != solved_rows.rend(); ++it) + tab.remove_row(*it); + for (auto it = solved_ins.rbegin(); it != solved_ins.rend(); ++it) + tab.discard_qubit(*it, ChoiMixTableau::TableauSegment::Input); + for (auto it = solved_outs.rbegin(); it != solved_outs.rend(); ++it) + tab.discard_qubit(*it, ChoiMixTableau::TableauSegment::Output); +} + +// Given a matrix that is already in upper echelon form, use the fact that the +// leading columns are already unique to give column operations that reduce it +// down to identity over the leading columns, eliminating extra swap gates to +// move to the first spaces +static std::vector> +leading_column_gaussian_col_ops(const MatrixXb& source) { + std::vector col_list; + std::set non_leads; + for (unsigned r = 0; r < source.rows(); ++r) { + bool leading_found = false; + for (unsigned c = 0; c < source.cols(); ++c) { + if (source(r, c)) { + if (leading_found) + non_leads.insert(c); + else { + leading_found = true; + col_list.push_back(c); + } } } } + for (const unsigned& c : non_leads) col_list.push_back(c); + MatrixXb reordered = MatrixXb::Zero(source.rows(), col_list.size()); + for (unsigned c = 0; c < col_list.size(); ++c) + reordered.col(c) = source.col(col_list.at(c)); + std::vector> reordered_ops = + gaussian_elimination_col_ops(reordered); + std::vector> res; + for (const std::pair& op : reordered_ops) + res.push_back({col_list.at(op.first), col_list.at(op.second)}); + return res; +} - // Remove rows with inputs from the tableau - tab.tab_ = SymplecticTableau( - tab.tab_.xmat_.bottomRows(out_stabs), - tab.tab_.zmat_.bottomRows(out_stabs), tab.tab_.phase_.tail(out_stabs)); - - // Can't use a template with multiple parameters within a macro since the - // comma will register as an argument delimiter for the macro - using match_entry = boost::bimap::left_const_reference; - BOOST_FOREACH (match_entry entry, matched_qubits.left) { - tab.discard_qubit(entry.first, ChoiMixTableau::TableauSegment::Input); - tab.discard_qubit(entry.second, ChoiMixTableau::TableauSegment::Output); - } +void ChoiMixBuilder::diagonalise_segments() { + // Canonicalise tableau tab.canonical_column_order(ChoiMixTableau::TableauSegment::Output); + tab.gaussian_form(); - // Only remaining rows must be completely over the outputs. Call - // diagonalisation methods to diagonalise coherent subspace - to_diag.clear(); + // Set up diagonalisation tasks + std::list to_diag_ins, to_diag_outs; for (unsigned r = 0; r < tab.get_n_rows(); ++r) { ChoiMixTableau::row_tensor_t rten = tab.get_row(r); - to_diag.push_back(rten.second); + if (!rten.first.string.empty()) to_diag_ins.push_back(rten.first); + if (!rten.second.string.empty()) to_diag_outs.push_back(rten.second); } - std::set diag_outs; - for (const Qubit& out : output_qubits) { - if (matched_qubits.right.find(out) == matched_qubits.right.end()) - diag_outs.insert(out); + qubit_vector_t input_qubits = tab.input_qubits(); + std::set diag_ins{input_qubits.begin(), input_qubits.end()}; + Circuit in_diag_circ = mutual_diagonalise(to_diag_ins, diag_ins, cx_config); + for (const Command& com : in_diag_circ) { + auto args = com.get_args(); + in_circ.add_op(com.get_op_ptr(), args); + qubit_vector_t qbs = {args.begin(), args.end()}; + tab.apply_gate( + com.get_op_ptr()->dagger()->get_type(), qbs, + ChoiMixTableau::TableauSegment::Input); } + qubit_vector_t output_qubits = tab.output_qubits(); + std::set diag_outs{output_qubits.begin(), output_qubits.end()}; Circuit out_diag_circ = - mutual_diagonalise(to_diag, diag_outs, CXConfigType::Tree); - // Extract the dagger of each gate in order from tab + mutual_diagonalise(to_diag_outs, diag_outs, cx_config); for (const Command& com : out_diag_circ) { auto args = com.get_args(); + out_circ_tp.add_op(com.get_op_ptr()->dagger()->transpose(), args); qubit_vector_t qbs = {args.begin(), args.end()}; - tab.apply_gate(com.get_op_ptr()->get_type(), qbs); - out_circ_tp.add_op(com.get_op_ptr()->transpose()->dagger(), qbs); + tab.apply_gate( + com.get_op_ptr()->get_type(), qbs, + ChoiMixTableau::TableauSegment::Output); } - // All rows are diagonalised so we can just focus on the Z matrix. Reduce them - // to a minimal set of qubits for initialisation by first reducing to upper - // echelon form - row_ops = gaussian_elimination_row_ops(tab.tab_.zmat_); - for (const std::pair& op : row_ops) { - tab.tab_.row_mult(op.first, op.second); + // All rows are diagonalised, so we can just focus on the Z matrix + if (tab.tab_.xmat != MatrixXb::Zero(tab.get_n_rows(), tab.get_n_boundaries())) + throw std::logic_error( + "Diagonalisation in ChoiMixTableau synthesis failed"); +} + +void ChoiMixBuilder::solve_postselected_subspace() { + // As column order is currently output first, gaussian form will reveal the + // post-selected space at the bottom of the tableau and the submatrix of those + // rows will already be in upper echelon form + tab.gaussian_form(); + // Reduce them to a minimal set of qubits using CX gates + unsigned n_postselected = 0; + for (; n_postselected < tab.get_n_rows(); ++n_postselected) { + if (!tab.get_row(tab.get_n_rows() - 1 - n_postselected) + .second.string.empty()) + break; } - // Obtain CX instructions as column operations - col_ops = gaussian_elimination_col_ops(tab.tab_.zmat_); + unsigned n_ins = tab.get_n_inputs(); + unsigned n_outs = tab.get_n_outputs(); + MatrixXb subtableau = tab.tab_.zmat.bottomRightCorner(n_postselected, n_ins); + std::vector> col_ops = + leading_column_gaussian_col_ops(subtableau); for (const std::pair& op : col_ops) { - tab.tab_.apply_CX(op.second, op.first); - ChoiMixTableau::col_key_t ctrl = tab.col_index_.right.at(op.second); - ChoiMixTableau::col_key_t trgt = tab.col_index_.right.at(op.first); - out_circ_tp.add_op(OpType::CX, {ctrl.first, trgt.first}); + unsigned tab_ctrl_col = n_outs + op.second; + unsigned tab_trgt_col = n_outs + op.first; + tab.tab_.apply_CX(tab_ctrl_col, tab_trgt_col); + ChoiMixTableau::col_key_t ctrl = tab.col_index_.right.at(tab_ctrl_col); + ChoiMixTableau::col_key_t trgt = tab.col_index_.right.at(tab_trgt_col); + in_circ.add_op(OpType::CX, {ctrl.first, trgt.first}); } + // Postselect rows + for (unsigned r = 0; r < n_postselected; ++r) { + unsigned final_row = tab.get_n_rows() - 1; + ChoiMixTableau::row_tensor_t row = tab.get_row(final_row); + if (row.second.size() != 0 || row.first.size() != 1 || + row.first.string.begin()->second != Pauli::Z) + throw std::logic_error( + "Unexpected error during post-selection identification in " + "ChoiMixTableau synthesis"); + Qubit post_selected_qb = row.first.string.begin()->first; + // Multiply other rows to remove Z_qb components + unsigned qb_col = tab.col_index_.left.at(ChoiMixTableau::col_key_t{ + post_selected_qb, ChoiMixTableau::TableauSegment::Input}); + for (unsigned s = 0; s < final_row; ++s) + if (tab.tab_.zmat(s, qb_col)) tab.tab_.row_mult(final_row, s); + // Post-select on correct phase + if (row.second.is_real_negative()) + in_circ.add_op(OpType::X, {post_selected_qb}); + tab.remove_row(final_row); + post_selected.insert(post_selected_qb); + tab.discard_qubit(post_selected_qb, ChoiMixTableau::TableauSegment::Input); + } +} - // Fix phases of zero_initialised qubits - std::set zero_initialised; - Circuit out_circ(output_qubits, {}); - for (unsigned r = 0; r < tab.get_n_rows(); ++r) { +void ChoiMixBuilder::solve_initialised_subspace() { + // Input-first gaussian elimination now sorts the remaining rows into the + // collapsed subspace followed by the zero-initialised subspace and the + // collapsed subspace rows are in upper echelon form over the inputs, giving + // unique leading columns and allowing us to solve them with CXs by + // column-wise gaussian elimination; same for zero-initialised rows over the + // outputs + tab.canonical_column_order(ChoiMixTableau::TableauSegment::Input); + tab.gaussian_form(); + + // Reduce the zero-initialised space to a minimal set of qubits using CX gates + unsigned n_collapsed = 0; + for (; n_collapsed < tab.get_n_rows(); ++n_collapsed) { + if (tab.get_row(n_collapsed).first.string.empty()) break; + } + unsigned n_ins = tab.get_n_inputs(); + unsigned n_outs = tab.get_n_outputs(); + MatrixXb subtableau = + tab.tab_.zmat.bottomRightCorner(tab.get_n_rows() - n_collapsed, n_outs); + std::vector> col_ops = + leading_column_gaussian_col_ops(subtableau); + for (const std::pair& op : col_ops) { + unsigned tab_ctrl_col = n_ins + op.second; + unsigned tab_trgt_col = n_ins + op.first; + tab.tab_.apply_CX(tab_ctrl_col, tab_trgt_col); + ChoiMixTableau::col_key_t ctrl = tab.col_index_.right.at(tab_ctrl_col); + ChoiMixTableau::col_key_t trgt = tab.col_index_.right.at(tab_trgt_col); + out_circ_tp.add_op(OpType::CX, {ctrl.first, trgt.first}); + } + // Initialise rows + for (unsigned r = tab.get_n_rows(); r-- > n_collapsed;) { + // r always refers to the final row in the tableau ChoiMixTableau::row_tensor_t row = tab.get_row(r); if (row.first.size() != 0 || row.second.size() != 1 || row.second.string.begin()->second != Pauli::Z) throw std::logic_error( - "Unexpected error during zero initialisation in ChoiMixTableau " - "synthesis"); + "Unexpected error during initialisation identification in " + "ChoiMixTableau synthesis"); Qubit initialised_qb = row.second.string.begin()->first; - out_circ.qubit_create(initialised_qb); - if (row.second.is_real_negative()) { - out_circ.add_op(OpType::X, {initialised_qb}); - } + // Multiply other rows to remove Z_qb components + unsigned qb_col = tab.col_index_.left.at(ChoiMixTableau::col_key_t{ + initialised_qb, ChoiMixTableau::TableauSegment::Output}); + for (unsigned s = 0; s < r; ++s) + if (tab.tab_.zmat(s, qb_col)) tab.tab_.row_mult(r, s); + // Initialise with correct phase + if (row.second.is_real_negative()) + out_circ_tp.add_op(OpType::X, {initialised_qb}); + tab.remove_row(r); zero_initialised.insert(initialised_qb); + tab.discard_qubit(initialised_qb, ChoiMixTableau::TableauSegment::Output); } +} - // Remaining outputs that aren't zero initialised or matched need to be - // initialised in the maximally-mixed state. - // Also match up unmatched outputs to either unmatched inputs or reusable - // output names (ones that are already matched up to other input names), - // preferring the qubit of the same name - std::list reusable_names; - for (const Qubit& out_qb : output_qubits) { - if (matched_qubits.right.find(out_qb) != matched_qubits.right.end() && - matched_qubits.left.find(out_qb) == matched_qubits.left.end()) - reusable_names.push_back(out_qb); - } - for (const Qubit& out_qb : output_qubits) { - if (matched_qubits.right.find(out_qb) == matched_qubits.right.end()) { - if (zero_initialised.find(out_qb) == zero_initialised.end()) { - out_circ.qubit_create(out_qb); - out_circ.add_op(OpType::H, {out_qb}); - out_circ.add_op(OpType::Collapse, {out_qb}); +void ChoiMixBuilder::solve_collapsed_subspace() { + // Solving the initialised subspace will have preserved the upper echelon form + // of the collapsed subspace; reduce the inputs of the collapsed space to a + // minimal set of qubits using CX gates + unsigned n_ins = tab.get_n_inputs(); + unsigned n_outs = tab.get_n_outputs(); + MatrixXb subtableau = tab.tab_.zmat.topLeftCorner(tab.get_n_rows(), n_ins); + std::vector> col_ops = + leading_column_gaussian_col_ops(subtableau); + for (const std::pair& op : col_ops) { + tab.tab_.apply_CX(op.second, op.first); + ChoiMixTableau::col_key_t ctrl = tab.col_index_.right.at(op.second); + ChoiMixTableau::col_key_t trgt = tab.col_index_.right.at(op.first); + in_circ.add_op(OpType::CX, {ctrl.first, trgt.first}); + } + // Since row multiplications will unsolve the inputs, we cannot get the output + // segment into upper echelon form for the same CX-saving trick; instead we + // accept just removing any qubits that are now unused after solving the + // initialised subspace + remove_unused_qubits(); + tab.canonical_column_order(ChoiMixTableau::TableauSegment::Input); + // Solve the output segment using CX gates + n_ins = tab.get_n_inputs(); + n_outs = tab.get_n_outputs(); + col_ops = gaussian_elimination_col_ops( + tab.tab_.zmat.topRightCorner(tab.get_n_rows(), n_outs)); + for (const std::pair& op : col_ops) { + tab.tab_.apply_CX(n_ins + op.second, n_ins + op.first); + ChoiMixTableau::col_key_t ctrl = tab.col_index_.right.at(n_ins + op.second); + ChoiMixTableau::col_key_t trgt = tab.col_index_.right.at(n_ins + op.first); + out_circ_tp.add_op(OpType::CX, {ctrl.first, trgt.first}); + } + // Connect up and remove rows and columns + for (unsigned r = tab.get_n_rows(); r-- > 0;) { + // r refers to the final row + // Check that row r has been successfully reduced + ChoiMixTableau::row_tensor_t row_r = tab.get_row(r); + if (row_r.first.size() != 1 || + row_r.first.string.begin()->second != Pauli::Z || + row_r.second.size() != 1 || + row_r.second.string.begin()->second != Pauli::Z) + throw std::logic_error( + "Unexpected error during collapsed subspace reduction in " + "ChoiMixTableau synthesis"); + Qubit in_q = row_r.first.string.begin()->first; + Qubit out_q = row_r.second.string.begin()->first; + // Solve phase + if (row_r.second.is_real_negative()) { + in_circ.add_op(OpType::X, {in_q}); + tab.apply_gate(OpType::X, {in_q}, ChoiMixTableau::TableauSegment::Input); + } + // Connect in permutation + in_out_permutation.insert({in_q, out_q}); + collapsed.insert(in_q); + tab.remove_row(r); + tab.discard_qubit(in_q, ChoiMixTableau::TableauSegment::Input); + tab.discard_qubit(out_q, ChoiMixTableau::TableauSegment::Output); + } +} + +void ChoiMixBuilder::remove_unused_qubits() { + // Since removing a column replaces it with the last column, remove in reverse + // order to examine each column exactly once + for (unsigned c = tab.get_n_boundaries(); c-- > 0;) { + bool used = false; + for (unsigned r = 0; r < tab.get_n_rows(); ++r) { + if (tab.tab_.zmat(r, c) || tab.tab_.xmat(r, c)) { + used = true; + break; } - if (matched_qubits.left.find(out_qb) == matched_qubits.left.end()) { - matched_qubits.insert({out_qb, out_qb}); - join_permutation.insert({out_qb, out_qb}); - } else { - // Since the matching is a bijection, matched_qubits.left - - // matched_qubits.right (set difference) is the same size as - // matched_qubits.right - matched_qubits.left, so there will be exactly - // the right number of reusable names to pull from here - Qubit name = reusable_names.front(); - reusable_names.pop_front(); - matched_qubits.insert({name, out_qb}); - join_permutation.insert({out_qb, name}); + } + if (used) continue; + ChoiMixTableau::col_key_t col = tab.col_index_.right.at(c); + if (col.second == ChoiMixTableau::TableauSegment::Input) + discarded.insert(col.first); + else + mix_initialised.insert(col.first); + tab.discard_qubit(col.first, col.second); + } +} + +void ChoiMixBuilder::assign_init_post_names() { + auto it = unitary_post_names.begin(); + for (const Qubit& ps : post_selected) { + if (it == unitary_post_names.end()) + throw std::logic_error( + "Not enough additional qubit names for unitary extension of " + "ChoiMixTableau to safely handle post-selected subspace"); + in_out_permutation.insert({ps, *it}); + ++it; + } + unitary_post_names = {it, unitary_post_names.end()}; + + it = unitary_init_names.begin(); + for (const Qubit& zi : zero_initialised) { + if (it == unitary_init_names.end()) + throw std::logic_error( + "Not enough additional qubit names for unitary extension of " + "ChoiMixTableau to safely handle initialised subspace"); + in_out_permutation.insert({*it, zi}); + ++it; + } + unitary_init_names = {it, unitary_init_names.end()}; +} + +void ChoiMixBuilder::assign_remaining_names() { + // Some post-selected or initialised qubits might have already been matched up + // for unitary synthesis, so we only need to match up the remainder + std::set unsolved_ins = discarded; + for (const Qubit& q : post_selected) { + if (in_out_permutation.left.find(q) == in_out_permutation.left.end()) + unsolved_ins.insert(q); + } + std::set unsolved_outs = mix_initialised; + for (const Qubit& q : zero_initialised) { + if (in_out_permutation.right.find(q) == in_out_permutation.right.end()) + unsolved_outs.insert(q); + } + // If there are more unsolved_ins than unsolved_outs, we want to pad out + // unsolved_outs with extra names that don't appear as output names of the + // original tableau; between unsolved_ins and the inputs already in + // in_out_permutation, there will be at least enough of these + if (unsolved_ins.size() > unsolved_outs.size()) { + for (const Qubit& q : unsolved_ins) { + if (in_out_permutation.right.find(q) == in_out_permutation.right.end()) { + unsolved_outs.insert(q); + if (unsolved_ins.size() == unsolved_outs.size()) break; + } + } + if (unsolved_ins.size() > unsolved_outs.size()) { + using perm_entry = boost::bimap::left_const_reference; + BOOST_FOREACH (perm_entry entry, in_out_permutation.left) { + if (in_out_permutation.right.find(entry.first) == + in_out_permutation.right.end()) { + unsolved_outs.insert(entry.first); + if (unsolved_ins.size() == unsolved_outs.size()) break; + } + } + } + } else if (unsolved_ins.size() < unsolved_outs.size()) { + for (const Qubit& q : unsolved_outs) { + if (in_out_permutation.left.find(q) == in_out_permutation.left.end()) { + unsolved_ins.insert(q); + if (unsolved_ins.size() == unsolved_outs.size()) break; + } + } + if (unsolved_ins.size() < unsolved_outs.size()) { + using perm_entry = boost::bimap::left_const_reference; + BOOST_FOREACH (perm_entry entry, in_out_permutation.left) { + if (in_out_permutation.left.find(entry.second) == + in_out_permutation.left.end()) { + unsolved_ins.insert(entry.second); + if (unsolved_ins.size() == unsolved_outs.size()) break; + } } } } + // Prefer to connect qubits with the same names + for (auto in_it = unsolved_ins.begin(); in_it != unsolved_ins.end();) { + auto temp_it = in_it++; + auto out_it = unsolved_outs.find(*temp_it); + if (out_it != unsolved_outs.end()) { + in_out_permutation.insert({*temp_it, *temp_it}); + unsolved_ins.erase(temp_it); + unsolved_outs.erase(out_it); + } + } + // Pair up remainders; by our earlier padding, they should have the exact same + // number of elements, so pair them up exactly + for (const Qubit& in : unsolved_ins) { + auto it = unsolved_outs.begin(); + in_out_permutation.insert({in, *it}); + unsolved_outs.erase(it); + } +} - // Initialise qubits with stabilizer rows and stitch subcircuits together +std::pair ChoiMixBuilder::output_circuit() { + if (tab.get_n_rows() != 0 || tab.get_n_boundaries() != 0) + throw std::logic_error( + "Unexpected error during ChoiMixTableau synthesis, reached the end " + "with a non-empty tableau remaining"); + if (!post_selected.empty()) { + throw std::logic_error( + "Not yet implemented: post-selection required during ChoiMixTableau " + "synthesis"); + } + for (const Qubit& q : discarded) in_circ.qubit_discard(q); + for (const Qubit& q : collapsed) in_circ.add_op(OpType::Collapse, {q}); + Circuit out_circ(out_circ_tp.all_qubits(), {}); + for (const Qubit& q : zero_initialised) out_circ.qubit_create(q); + for (const Qubit& q : mix_initialised) { + out_circ.qubit_create(q); + out_circ.add_op(OpType::H, {q}); + out_circ.add_op(OpType::Collapse, {q}); + } out_circ.append(out_circ_tp.transpose()); - in_circ.append_with_map(out_circ, join_permutation); - return {in_circ, join_permutation}; + qubit_map_t return_perm; + unit_map_t append_perm; + using perm_entry = boost::bimap::left_const_reference; + BOOST_FOREACH (perm_entry entry, in_out_permutation.left) { + return_perm.insert({entry.second, entry.first}); + append_perm.insert({entry.second, entry.first}); + } + in_circ.append_with_map(out_circ, append_perm); + return {in_circ, return_perm}; +} + +std::pair ChoiMixBuilder::unitary_output_circuit() { + if (tab.get_n_rows() != 0 || tab.get_n_boundaries() != 0) + throw std::logic_error( + "Unexpected error during ChoiMixTableau synthesis, reached the end " + "with a non-empty tableau remaining"); + qubit_map_t return_perm; + unit_map_t append_perm; + using perm_entry = boost::bimap::left_const_reference; + BOOST_FOREACH (perm_entry entry, in_out_permutation.left) { + return_perm.insert({entry.second, entry.first}); + append_perm.insert({entry.second, entry.first}); + } + in_circ.append_with_map(out_circ_tp.transpose(), append_perm); + return {in_circ, return_perm}; } } // namespace tket diff --git a/tket/src/Converters/PauliGadget.cpp b/tket/src/Converters/PauliGadget.cpp deleted file mode 100644 index 941f90ea9b..0000000000 --- a/tket/src/Converters/PauliGadget.cpp +++ /dev/null @@ -1,381 +0,0 @@ -// Copyright 2019-2024 Cambridge Quantum Computing -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "tket/Converters/PauliGadget.hpp" - -#include "tket/Circuit/CircUtils.hpp" -#include "tket/Circuit/ConjugationBox.hpp" -#include "tket/Circuit/PauliExpBoxes.hpp" - -namespace tket { - -void append_single_pauli_gadget( - Circuit &circ, const SpSymPauliTensor &pauli, CXConfigType cx_config) { - std::vector string; - unit_map_t mapping; - unsigned i = 0; - for (const std::pair &term : pauli.string) { - string.push_back(term.second); - mapping.insert({Qubit(q_default_reg(), i), term.first}); - i++; - } - Circuit gadget = pauli_gadget(string, pauli.coeff, cx_config); - circ.append_with_map(gadget, mapping); -} - -void append_single_pauli_gadget_as_pauli_exp_box( - Circuit &circ, const SpSymPauliTensor &pauli, CXConfigType cx_config) { - std::vector string; - std::vector mapping; - for (const std::pair &term : pauli.string) { - string.push_back(term.second); - mapping.push_back(term.first); - } - PauliExpBox box(SymPauliTensor(string, pauli.coeff), cx_config); - circ.add_box(box, mapping); -} - -void append_pauli_gadget_pair_as_box( - Circuit &circ, const SpSymPauliTensor &pauli0, - const SpSymPauliTensor &pauli1, CXConfigType cx_config) { - std::vector mapping; - std::vector paulis0; - std::vector paulis1; - QubitPauliMap p1map = pauli1.string; - // add paulis for qubits in pauli0_string - for (const std::pair &term : pauli0.string) { - mapping.push_back(term.first); - paulis0.push_back(term.second); - auto found = p1map.find(term.first); - if (found == p1map.end()) { - paulis1.push_back(Pauli::I); - } else { - paulis1.push_back(found->second); - p1map.erase(found); - } - } - // add paulis for qubits in pauli1_string that weren't in pauli0_string - for (const std::pair &term : p1map) { - mapping.push_back(term.first); - paulis1.push_back(term.second); - paulis0.push_back(Pauli::I); // If pauli0_string contained qubit, would - // have been handled above - } - PauliExpPairBox box( - SymPauliTensor(paulis0, pauli0.coeff), - SymPauliTensor(paulis1, pauli1.coeff), cx_config); - circ.add_box(box, mapping); -} - -void append_commuting_pauli_gadget_set_as_box( - Circuit &circ, const std::list &gadgets, - CXConfigType cx_config) { - // Translate SpSymPauliTensors to vectors of Paulis of same length - // Preserves ordering of qubits - - std::set all_qubits; - for (const SpSymPauliTensor &gadget : gadgets) { - for (const std::pair &qubit_pauli : gadget.string) { - all_qubits.insert(qubit_pauli.first); - } - } - - std::vector mapping; - for (const Qubit &qubit : all_qubits) { - mapping.push_back(qubit); - } - - std::vector pauli_gadgets; - for (const SpSymPauliTensor &gadget : gadgets) { - SymPauliTensor &new_gadget = - pauli_gadgets.emplace_back(DensePauliMap{}, gadget.coeff); - for (const Qubit &qubit : mapping) { - new_gadget.string.push_back(gadget.get(qubit)); - } - } - - PauliExpCommutingSetBox box(pauli_gadgets, cx_config); - circ.add_box(box, mapping); -} - -static void reduce_shared_qs_by_CX_snake( - Circuit &circ, std::set &match, SpSymPauliTensor &pauli0, - SpSymPauliTensor &pauli1) { - unsigned match_size = match.size(); - while (match_size > 1) { // We allow one match left over - auto it = --match.end(); - Qubit to_eliminate = *it; - match.erase(it); - Qubit helper = *match.rbegin(); - // extend CX snake - circ.add_op(OpType::CX, {to_eliminate, helper}); - pauli0.string.erase(to_eliminate); - pauli1.string.erase(to_eliminate); - match_size--; - } -} - -static void reduce_shared_qs_by_CX_star( - Circuit &circ, std::set &match, SpSymPauliTensor &pauli0, - SpSymPauliTensor &pauli1) { - std::set::iterator iter = match.begin(); - for (std::set::iterator next = match.begin(); match.size() > 1; - iter = next) { - ++next; - Qubit to_eliminate = *iter; - circ.add_op(OpType::CX, {to_eliminate, *match.rbegin()}); - pauli0.string.erase(to_eliminate); - pauli1.string.erase(to_eliminate); - match.erase(iter); - } -} - -static void reduce_shared_qs_by_CX_tree( - Circuit &circ, std::set &match, SpSymPauliTensor &pauli0, - SpSymPauliTensor &pauli1) { - while (match.size() > 1) { - std::set remaining; - std::set::iterator it = match.begin(); - while (it != match.end()) { - Qubit maintained = *it; - it++; - remaining.insert(maintained); - if (it != match.end()) { - Qubit to_eliminate = *it; - it++; - circ.add_op(OpType::CX, {to_eliminate, maintained}); - pauli0.string.erase(to_eliminate); - pauli1.string.erase(to_eliminate); - } - } - match = remaining; - } -} - -static void reduce_shared_qs_by_CX_multiqgate( - Circuit &circ, std::set &match, SpSymPauliTensor &pauli0, - SpSymPauliTensor &pauli1) { - if (match.size() <= 1) { - return; - } - // last qubit is target - Qubit target = *match.rbegin(); - while (match.size() > 1) { - std::set::iterator iter = match.begin(); - if (match.size() == 2) { - // use CX - Qubit to_eliminate = *iter; - match.erase(iter); - pauli0.string.erase(to_eliminate); - pauli1.string.erase(to_eliminate); - - circ.add_op(OpType::CX, {to_eliminate, target}); - } else { - // use XXPhase3 - Qubit to_eliminate1 = *iter; - match.erase(iter++); - pauli0.string.erase(to_eliminate1); - pauli1.string.erase(to_eliminate1); - - Qubit to_eliminate2 = *iter; - match.erase(iter); - pauli0.string.erase(to_eliminate2); - pauli1.string.erase(to_eliminate2); - - circ.add_op(OpType::H, {to_eliminate1}); - circ.add_op(OpType::H, {to_eliminate2}); - circ.add_op( - OpType::XXPhase3, 0.5, {to_eliminate1, to_eliminate2, target}); - circ.add_op(OpType::X, {target}); - } - } -} - -void append_pauli_gadget_pair( - Circuit &circ, SpSymPauliTensor pauli0, SpSymPauliTensor pauli1, - CXConfigType cx_config) { - /* - * Cowtan, Dilkes, Duncan, Simmons, Sivarajah: Phase Gadget Synthesis for - * Shallow Circuits, Lemma 4.9 - * Let s and t be Pauli strings; then there exists a Clifford unitary U such - * that - * P(a, s) . P(b, t) = U . P(a, s') . P(b, t') . U^\dagger - * where s' and t' are Pauli strings with intersection at most 1. - * - * Follows the procedure to reduce the intersection of the gadgets and then - * synthesises the remainder individually. - */ - pauli0.compress(); - pauli1.compress(); - - /* - * Step 1: Partition qubits into those just affected by pauli0 (just0) and - * pauli1 (just1), and those in both which either match or don't - */ - std::set just0 = pauli0.own_qubits(pauli1); - std::set just1 = pauli1.own_qubits(pauli0); - std::set match = pauli0.common_qubits(pauli1); - std::set mismatch = pauli0.conflicting_qubits(pauli1); - - /* - * Step 2: Build the unitary U that minimises the intersection of the gadgets. - */ - Circuit u; - for (const Qubit &qb : just0) u.add_qubit(qb); - for (const Qubit &qb : just1) u.add_qubit(qb); - for (const Qubit &qb : match) u.add_qubit(qb); - for (const Qubit &qb : mismatch) u.add_qubit(qb); - Circuit v(u); - - /* - * Step 2.i: Remove (almost) all matches by converting to Z basis and applying - * CXs - */ - for (const Qubit &qb : match) { - switch (pauli0.get(qb)) { - case Pauli::X: - u.add_op(OpType::H, {qb}); - pauli0.set(qb, Pauli::Z); - pauli1.set(qb, Pauli::Z); - break; - case Pauli::Y: - u.add_op(OpType::V, {qb}); - pauli0.set(qb, Pauli::Z); - pauli1.set(qb, Pauli::Z); - break; - default: - break; - } - } - switch (cx_config) { - case CXConfigType::Snake: { - reduce_shared_qs_by_CX_snake(u, match, pauli0, pauli1); - break; - } - case CXConfigType::Star: { - reduce_shared_qs_by_CX_star(u, match, pauli0, pauli1); - break; - } - case CXConfigType::Tree: { - reduce_shared_qs_by_CX_tree(u, match, pauli0, pauli1); - break; - } - case CXConfigType::MultiQGate: { - reduce_shared_qs_by_CX_multiqgate(u, match, pauli0, pauli1); - break; - } - default: - throw std::logic_error( - "Unknown CXConfigType received when decomposing gadget."); - } - /* - * Step 2.ii: Convert mismatches to Z in pauli0 and X in pauli1 - */ - for (const Qubit &qb : mismatch) { - switch (pauli0.get(qb)) { - case Pauli::X: { - switch (pauli1.get(qb)) { - case Pauli::Y: - u.add_op(OpType::Sdg, {qb}); - u.add_op(OpType::Vdg, {qb}); - break; - case Pauli::Z: - u.add_op(OpType::H, {qb}); - break; - default: - break; // Cannot hit this case - } - break; - } - case Pauli::Y: { - switch (pauli1.get(qb)) { - case Pauli::X: - u.add_op(OpType::V, {qb}); - break; - case Pauli::Z: - u.add_op(OpType::V, {qb}); - u.add_op(OpType::S, {qb}); - break; - default: - break; // Cannot hit this case - } - break; - } - default: { // Necessarily Z - if (pauli1.get(qb) == Pauli::Y) u.add_op(OpType::Sdg, {qb}); - // No need to act if already X - } - } - pauli0.set(qb, Pauli::Z); - pauli1.set(qb, Pauli::X); - } - - /* - * Step 2.iii: Remove the final matching qubit against a mismatch if one - * exists, otherwise allow both gadgets to build it - */ - if (!match.empty()) { - Qubit last_match = *match.begin(); - match.erase(last_match); - if (!mismatch.empty()) { - Qubit mismatch_used = - *mismatch.rbegin(); // Prefer to use the one that may be left over - // after reducing pairs - u.add_op(OpType::S, {mismatch_used}); - u.add_op(OpType::CX, {last_match, mismatch_used}); - u.add_op(OpType::Sdg, {mismatch_used}); - pauli0.string.erase(last_match); - pauli1.string.erase(last_match); - } else { - just0.insert(last_match); - just1.insert(last_match); - } - } - - /* - * Step 2.iv: Reduce pairs of mismatches to different qubits. - * Allow both gadgets to build a remaining qubit if it exists. - */ - std::set::iterator mis_it = mismatch.begin(); - while (mis_it != mismatch.end()) { - Qubit z_in_0 = *mis_it; - just0.insert(z_in_0); - mis_it++; - if (mis_it == mismatch.end()) { - just1.insert(z_in_0); - } else { - Qubit x_in_1 = *mis_it; - u.add_op(OpType::CX, {x_in_1, z_in_0}); - pauli0.string.erase(x_in_1); - pauli1.string.erase(z_in_0); - just1.insert(x_in_1); - mis_it++; - } - } - - /* - * Step 3: Combine circuits to give final result - */ - append_single_pauli_gadget(v, pauli0); - append_single_pauli_gadget(v, pauli1); - // ConjugationBox components must be in the default register - qubit_vector_t all_qubits = u.all_qubits(); - u.flatten_registers(); - v.flatten_registers(); - ConjugationBox cjbox( - std::make_shared(u), std::make_shared(v)); - circ.add_box(cjbox, all_qubits); -} - -} // namespace tket diff --git a/tket/src/Converters/PauliGraphConverters.cpp b/tket/src/Converters/PauliGraphConverters.cpp index e04d58ff55..46a3126433 100644 --- a/tket/src/Converters/PauliGraphConverters.cpp +++ b/tket/src/Converters/PauliGraphConverters.cpp @@ -15,7 +15,6 @@ #include "tket/Circuit/Boxes.hpp" #include "tket/Circuit/PauliExpBoxes.hpp" #include "tket/Converters/Converters.hpp" -#include "tket/Converters/PauliGadget.hpp" #include "tket/Converters/PhasePoly.hpp" #include "tket/Diagonalisation/Diagonalisation.hpp" #include "tket/Gate/Gate.hpp" diff --git a/tket/src/Converters/UnitaryTableauConverters.cpp b/tket/src/Converters/UnitaryTableauConverters.cpp index 22ad4dfbef..8631cfa1a3 100644 --- a/tket/src/Converters/UnitaryTableauConverters.cpp +++ b/tket/src/Converters/UnitaryTableauConverters.cpp @@ -44,7 +44,7 @@ Circuit unitary_tableau_to_circuit(const UnitaryTableau& tab) { * Step 1: Use Hadamards (in our case, Vs) to make C (z rows of xmat_) have * full rank */ - MatrixXb echelon = tabl.xmat_.block(size, 0, size, size); + MatrixXb echelon = tabl.xmat.block(size, 0, size, size); std::map leading_val_to_col; for (unsigned i = 0; i < size; i++) { for (unsigned j = 0; j < size; j++) { @@ -64,9 +64,8 @@ Circuit unitary_tableau_to_circuit(const UnitaryTableau& tab) { continue; // Independent of previous cols c.add_op(OpType::V, {i}); tabl.apply_V(i); - tabl.apply_V(i); - tabl.apply_V(i); - echelon.col(i) = tabl.zmat_.block(size, i, size, 1); + tabl.apply_X(i); + echelon.col(i) = tabl.zmat.block(size, i, size, 1); for (unsigned j = 0; j < size; j++) { if (echelon(j, i)) { if (leading_val_to_col.find(j) == leading_val_to_col.end()) { @@ -89,7 +88,7 @@ Circuit unitary_tableau_to_circuit(const UnitaryTableau& tab) { * / A B \ * \ I D / */ - MatrixXb to_reduce = tabl.xmat_.block(size, 0, size, size); + MatrixXb to_reduce = tabl.xmat.block(size, 0, size, size); for (const std::pair& qbs : gaussian_elimination_col_ops(to_reduce)) { c.add_op(OpType::CX, {qbs.first, qbs.second}); @@ -103,13 +102,12 @@ Circuit unitary_tableau_to_circuit(const UnitaryTableau& tab) { * for some invertible M. */ std::pair zp_z_llt = - binary_LLT_decomposition(tabl.zmat_.block(size, 0, size, size)); + binary_LLT_decomposition(tabl.zmat.block(size, 0, size, size)); for (unsigned i = 0; i < size; i++) { if (zp_z_llt.second(i, i)) { c.add_op(OpType::S, {i}); tabl.apply_S(i); - tabl.apply_S(i); - tabl.apply_S(i); + tabl.apply_Z(i); } } @@ -140,8 +138,7 @@ Circuit unitary_tableau_to_circuit(const UnitaryTableau& tab) { for (unsigned i = 0; i < size; i++) { c.add_op(OpType::S, {i}); tabl.apply_S(i); - tabl.apply_S(i); - tabl.apply_S(i); + tabl.apply_Z(i); } /* @@ -150,7 +147,7 @@ Circuit unitary_tableau_to_circuit(const UnitaryTableau& tab) { * \ I 0 / * By commutativity relations, IB^T = A0^T + I, therefore B = I. */ - to_reduce = tabl.xmat_.block(size, 0, size, size); + to_reduce = tabl.xmat.block(size, 0, size, size); for (const std::pair& qbs : gaussian_elimination_col_ops(to_reduce)) { c.add_op(OpType::CX, {qbs.first, qbs.second}); @@ -164,9 +161,7 @@ Circuit unitary_tableau_to_circuit(const UnitaryTableau& tab) { */ for (unsigned i = 0; i < size; i++) { c.add_op(OpType::H, {i}); - tabl.apply_S(i); - tabl.apply_V(i); - tabl.apply_S(i); + tabl.apply_H(i); } /* @@ -175,13 +170,12 @@ Circuit unitary_tableau_to_circuit(const UnitaryTableau& tab) { * some invertible N. */ std::pair xp_z_llt = - binary_LLT_decomposition(tabl.zmat_.block(0, 0, size, size)); + binary_LLT_decomposition(tabl.zmat.block(0, 0, size, size)); for (unsigned i = 0; i < size; i++) { if (xp_z_llt.second(i, i)) { c.add_op(OpType::S, {i}); tabl.apply_S(i); - tabl.apply_S(i); - tabl.apply_S(i); + tabl.apply_Z(i); } } @@ -210,8 +204,7 @@ Circuit unitary_tableau_to_circuit(const UnitaryTableau& tab) { for (unsigned i = 0; i < size; i++) { c.add_op(OpType::S, {i}); tabl.apply_S(i); - tabl.apply_S(i); - tabl.apply_S(i); + tabl.apply_Z(i); } /* @@ -220,7 +213,7 @@ Circuit unitary_tableau_to_circuit(const UnitaryTableau& tab) { * \ 0 I / */ for (const std::pair& qbs : - gaussian_elimination_col_ops(tabl.xmat_.block(0, 0, size, size))) { + gaussian_elimination_col_ops(tabl.xmat.block(0, 0, size, size))) { c.add_op(OpType::CX, {qbs.first, qbs.second}); tabl.apply_CX(qbs.first, qbs.second); } @@ -229,15 +222,13 @@ Circuit unitary_tableau_to_circuit(const UnitaryTableau& tab) { * DELAYED STEPS: Set all phases to 0 by applying Z or X gates */ for (unsigned i = 0; i < size; i++) { - if (tabl.phase_(i)) { + if (tabl.phase(i)) { c.add_op(OpType::Z, {i}); - tabl.apply_S(i); - tabl.apply_S(i); + tabl.apply_Z(i); } - if (tabl.phase_(i + size)) { + if (tabl.phase(i + size)) { c.add_op(OpType::X, {i}); - tabl.apply_V(i); - tabl.apply_V(i); + tabl.apply_X(i); } } diff --git a/tket/src/Diagonalisation/Diagonalisation.cpp b/tket/src/Diagonalisation/Diagonalisation.cpp index 7ab9d876db..6b433e10a3 100644 --- a/tket/src/Diagonalisation/Diagonalisation.cpp +++ b/tket/src/Diagonalisation/Diagonalisation.cpp @@ -342,4 +342,413 @@ void apply_conjugations( qps.coeff *= cast_coeff(stab.coeff); } +std::pair reduce_pauli_to_z( + const SpPauliStabiliser &pauli, CXConfigType cx_config) { + Circuit circ; + qubit_vector_t qubits; + for (const std::pair &qp : pauli.string) { + circ.add_qubit(qp.first); + if (qp.second != Pauli::I) qubits.push_back(qp.first); + switch (qp.second) { + case Pauli::X: { + circ.add_op(OpType::H, {qp.first}); + break; + } + case Pauli::Y: { + circ.add_op(OpType::V, {qp.first}); + break; + } + default: { + break; + } + } + } + unsigned n_qubits = qubits.size(); + if (n_qubits == 0) throw std::logic_error("Cannot reduce identity to Z"); + switch (cx_config) { + case CXConfigType::Snake: { + for (unsigned i = n_qubits - 1; i != 0; --i) { + circ.add_op(OpType::CX, {qubits.at(i), qubits.at(i - 1)}); + } + break; + } + case CXConfigType::Star: { + for (unsigned i = n_qubits - 1; i != 0; --i) { + circ.add_op(OpType::CX, {qubits.at(i), qubits.front()}); + } + break; + } + case CXConfigType::Tree: { + for (unsigned step_size = 1; step_size < n_qubits; step_size *= 2) { + for (unsigned i = 0; step_size + i < n_qubits; i += 2 * step_size) { + circ.add_op( + OpType::CX, {qubits.at(step_size + i), qubits.at(i)}); + } + } + break; + } + case CXConfigType::MultiQGate: { + bool flip_phase = false; + for (unsigned i = n_qubits - 1; i != 0; --i) { + if (i == 1) { + circ.add_op(OpType::CX, {qubits.at(i), qubits.front()}); + } else { + /** + * This is only equal to the CX decompositions above up to phase, + * but phase differences are cancelled out by its dagger + */ + circ.add_op(OpType::H, {qubits.at(i)}); + circ.add_op(OpType::H, {qubits.at(i - 1)}); + circ.add_op( + OpType::XXPhase3, 0.5, + {qubits.at(i), qubits.at(i - 1), qubits.front()}); + --i; + flip_phase = !flip_phase; + } + } + if (flip_phase) circ.add_op(OpType::X, {qubits.front()}); + break; + } + } + return {circ, qubits.front()}; +} + +static void reduce_shared_qs_by_CX_snake( + Circuit &circ, std::set &match, SpPauliStabiliser &pauli0, + SpPauliStabiliser &pauli1) { + unsigned match_size = match.size(); + while (match_size > 1) { // We allow one match left over + auto it = --match.end(); + Qubit to_eliminate = *it; + match.erase(it); + Qubit helper = *match.rbegin(); + // extend CX snake + circ.add_op(OpType::CX, {to_eliminate, helper}); + pauli0.string.erase(to_eliminate); + pauli1.string.erase(to_eliminate); + match_size--; + } +} + +static void reduce_shared_qs_by_CX_star( + Circuit &circ, std::set &match, SpPauliStabiliser &pauli0, + SpPauliStabiliser &pauli1) { + std::set::iterator iter = match.begin(); + for (std::set::iterator next = match.begin(); match.size() > 1; + iter = next) { + ++next; + Qubit to_eliminate = *iter; + circ.add_op(OpType::CX, {to_eliminate, *match.rbegin()}); + pauli0.string.erase(to_eliminate); + pauli1.string.erase(to_eliminate); + match.erase(iter); + } +} + +static void reduce_shared_qs_by_CX_tree( + Circuit &circ, std::set &match, SpPauliStabiliser &pauli0, + SpPauliStabiliser &pauli1) { + while (match.size() > 1) { + std::set remaining; + std::set::iterator it = match.begin(); + while (it != match.end()) { + Qubit maintained = *it; + it++; + remaining.insert(maintained); + if (it != match.end()) { + Qubit to_eliminate = *it; + it++; + circ.add_op(OpType::CX, {to_eliminate, maintained}); + pauli0.string.erase(to_eliminate); + pauli1.string.erase(to_eliminate); + } + } + match = remaining; + } +} + +static void reduce_shared_qs_by_CX_multiqgate( + Circuit &circ, std::set &match, SpPauliStabiliser &pauli0, + SpPauliStabiliser &pauli1) { + if (match.size() <= 1) { + return; + } + // last qubit is target + Qubit target = *match.rbegin(); + while (match.size() > 1) { + std::set::iterator iter = match.begin(); + if (match.size() == 2) { + // use CX + Qubit to_eliminate = *iter; + match.erase(iter); + pauli0.string.erase(to_eliminate); + pauli1.string.erase(to_eliminate); + + circ.add_op(OpType::CX, {to_eliminate, target}); + } else { + // use XXPhase3 + Qubit to_eliminate1 = *iter; + match.erase(iter++); + pauli0.string.erase(to_eliminate1); + pauli1.string.erase(to_eliminate1); + + Qubit to_eliminate2 = *iter; + match.erase(iter); + pauli0.string.erase(to_eliminate2); + pauli1.string.erase(to_eliminate2); + + circ.add_op(OpType::H, {to_eliminate1}); + circ.add_op(OpType::H, {to_eliminate2}); + circ.add_op( + OpType::XXPhase3, 0.5, {to_eliminate1, to_eliminate2, target}); + circ.add_op(OpType::X, {target}); + } + } +} + +std::pair> reduce_overlap_of_paulis( + SpPauliStabiliser &pauli0, SpPauliStabiliser &pauli1, + CXConfigType cx_config, bool allow_matching_final) { + /* + * Cowtan, Dilkes, Duncan, Simmons, Sivarajah: Phase Gadget Synthesis for + * Shallow Circuits, Lemma 4.9 + * Let s and t be Pauli strings; then there exists a Clifford unitary U such + * that + * P(a, s) . P(b, t) = U . P(a, s') . P(b, t') . U^\dagger + * where s' and t' are Pauli strings with intersection at most 1. + * + * Follows the procedure to reduce the intersection of the gadgets and then + * synthesises the remainder individually. + */ + + /* + * Step 1: Identify qubits in both pauli0 and pauli1 which either match or + * don't + */ + std::set match = pauli0.common_qubits(pauli1); + std::set mismatch = pauli0.conflicting_qubits(pauli1); + + /* + * Step 2: Build the unitary U that minimises the intersection of the gadgets. + */ + Circuit u; + for (const std::pair &qp : pauli0.string) + u.add_qubit(qp.first); + for (const std::pair &qp : pauli1.string) { + if (!u.contains_unit(qp.first)) u.add_qubit(qp.first); + } + + /* + * Step 2.i: Remove (almost) all matches by converting to Z basis and applying + * CXs + */ + for (const Qubit &qb : match) { + switch (pauli0.get(qb)) { + case Pauli::X: + u.add_op(OpType::H, {qb}); + pauli0.set(qb, Pauli::Z); + pauli1.set(qb, Pauli::Z); + break; + case Pauli::Y: + u.add_op(OpType::V, {qb}); + pauli0.set(qb, Pauli::Z); + pauli1.set(qb, Pauli::Z); + break; + default: + break; + } + } + switch (cx_config) { + case CXConfigType::Snake: { + reduce_shared_qs_by_CX_snake(u, match, pauli0, pauli1); + break; + } + case CXConfigType::Star: { + reduce_shared_qs_by_CX_star(u, match, pauli0, pauli1); + break; + } + case CXConfigType::Tree: { + reduce_shared_qs_by_CX_tree(u, match, pauli0, pauli1); + break; + } + case CXConfigType::MultiQGate: { + reduce_shared_qs_by_CX_multiqgate(u, match, pauli0, pauli1); + break; + } + default: + throw std::logic_error( + "Unknown CXConfigType received when decomposing gadget."); + } + /* + * Step 2.ii: Convert mismatches to Z in pauli0 and X in pauli1 + */ + for (const Qubit &qb : mismatch) { + switch (pauli0.get(qb)) { + case Pauli::X: { + switch (pauli1.get(qb)) { + case Pauli::Y: + u.add_op(OpType::Sdg, {qb}); + u.add_op(OpType::Vdg, {qb}); + break; + case Pauli::Z: + u.add_op(OpType::H, {qb}); + break; + default: + TKET_ASSERT(false); + break; // Cannot hit this case + } + break; + } + case Pauli::Y: { + switch (pauli1.get(qb)) { + case Pauli::X: + u.add_op(OpType::V, {qb}); + break; + case Pauli::Z: + u.add_op(OpType::V, {qb}); + u.add_op(OpType::S, {qb}); + break; + default: + TKET_ASSERT(false); + break; // Cannot hit this case + } + break; + } + default: { // Necessarily Z + if (pauli1.get(qb) == Pauli::Y) u.add_op(OpType::Sdg, {qb}); + // No need to act if already X + } + } + pauli0.set(qb, Pauli::Z); + pauli1.set(qb, Pauli::X); + } + + /* + * Step 2.iii: Remove the final matching qubit against a mismatch if one + * exists, otherwise remove into another qubit or allow final overlap to be + * matching + */ + std::optional last_overlap = std::nullopt; + if (!match.empty()) { + Qubit last_match = *match.begin(); + if (!mismatch.empty()) { + Qubit mismatch_used = + *mismatch.rbegin(); // Prefer to use the one that may be left over + // after reducing pairs + u.add_op(OpType::S, {mismatch_used}); + u.add_op(OpType::CX, {last_match, mismatch_used}); + u.add_op(OpType::Sdg, {mismatch_used}); + pauli0.string.erase(last_match); + pauli1.string.erase(last_match); + } else if (!allow_matching_final) { + std::optional> other; + for (const std::pair &qp : pauli0.string) { + if (qp.first != last_match && qp.second != Pauli::I) { + other = qp; + pauli0.string.erase(last_match); + break; + } + } + if (!other) { + for (const std::pair &qp : pauli1.string) { + if (qp.first != last_match && qp.second != Pauli::I) { + other = qp; + pauli1.string.erase(last_match); + break; + } + } + if (!other) + throw std::logic_error( + "Cannot reduce identical Paulis to different qubits"); + } + if (other->second == Pauli::X) { + u.add_op(OpType::H, {other->first}); + u.add_op(OpType::CX, {last_match, other->first}); + u.add_op(OpType::H, {other->first}); + } else { + u.add_op(OpType::CX, {last_match, other->first}); + } + } else { + last_overlap = last_match; + } + } + + /* + * Step 2.iv: Reduce pairs of mismatches to different qubits. + * Allow both gadgets to build a remaining qubit if it exists. + */ + std::set::iterator mis_it = mismatch.begin(); + while (mis_it != mismatch.end()) { + Qubit z_in_0 = *mis_it; + mis_it++; + if (mis_it != mismatch.end()) { + Qubit x_in_1 = *mis_it; + u.add_op(OpType::CX, {x_in_1, z_in_0}); + pauli0.string.erase(x_in_1); + pauli1.string.erase(z_in_0); + mis_it++; + } else { + last_overlap = z_in_0; + } + } + + return {u, last_overlap}; +} + +std::pair reduce_anticommuting_paulis_to_z_x( + SpPauliStabiliser pauli0, SpPauliStabiliser pauli1, + CXConfigType cx_config) { + std::pair> reduced_overlap = + reduce_overlap_of_paulis(pauli0, pauli1, cx_config); + Circuit &u = reduced_overlap.first; + if (!reduced_overlap.second) + throw std::logic_error("No overlap for anti-commuting paulis"); + Qubit &last_mismatch = *reduced_overlap.second; + + /** + * Reduce each remaining Pauli to the shared mismatching qubit. + * Since reduce_pauli_to_Z does not allow us to pick the final qubit, we + * reserve the mismatching qubit, call reduce_pauli_to_Z on the rest, and add + * a CX. + */ + pauli0.string.erase(last_mismatch); + pauli0.compress(); + if (!pauli0.string.empty()) { + std::pair diag0 = reduce_pauli_to_z(pauli0, cx_config); + u.append(diag0.first); + u.add_op(OpType::CX, {diag0.second, last_mismatch}); + } + pauli1.compress(); + pauli1.string.erase(last_mismatch); + if (!pauli1.string.empty()) { + std::pair diag1 = reduce_pauli_to_z(pauli1, cx_config); + u.append(diag1.first); + u.add_op(OpType::H, {last_mismatch}); + u.add_op(OpType::CX, {diag1.second, last_mismatch}); + u.add_op(OpType::H, {last_mismatch}); + } + + return {u, last_mismatch}; +} + +std::tuple reduce_commuting_paulis_to_zi_iz( + SpPauliStabiliser pauli0, SpPauliStabiliser pauli1, + CXConfigType cx_config) { + std::pair> reduced_overlap = + reduce_overlap_of_paulis(pauli0, pauli1, cx_config); + Circuit &u = reduced_overlap.first; + if (reduced_overlap.second) + throw std::logic_error("Overlap remaining for commuting paulis"); + + /** + * Reduce each remaining Pauli to a single qubit. + */ + std::pair diag0 = reduce_pauli_to_z(pauli0, cx_config); + u.append(diag0.first); + std::pair diag1 = reduce_pauli_to_z(pauli1, cx_config); + u.append(diag1.first); + + return {u, diag0.second, diag1.second}; +} + } // namespace tket diff --git a/tket/src/Gate/Gate.cpp b/tket/src/Gate/Gate.cpp index 62486dedc4..6dc93da255 100644 --- a/tket/src/Gate/Gate.cpp +++ b/tket/src/Gate/Gate.cpp @@ -118,6 +118,15 @@ Op_ptr Gate::dagger() const { case OpType::ESWAP: { return get_op_ptr(optype, minus_times(params_[0]), n_qubits_); } + case OpType::GPI: { + return get_op_ptr(optype, params_[0]); + } + case OpType::GPI2: { + return get_op_ptr(optype, params_[0] + 1); + } + case OpType::AAMS: { + return get_op_ptr(optype, {params_[0], params_[1] + 1, params_[2]}); + } case OpType::ZZMax: { // ZZMax.dagger = ZZPhase(-0.5) return get_op_ptr(OpType::ZZPhase, -0.5); @@ -232,6 +241,13 @@ Op_ptr Gate::transpose() const { case OpType::CnZ: { return get_op_ptr(optype, std::vector(), n_qubits_); } + case OpType::GPI: + case OpType::GPI2: { + return get_op_ptr(optype, -params_[0]); + } + case OpType::AAMS: { + return get_op_ptr(optype, {params_[0], -params_[1], -params_[2]}); + } case OpType::U2: { // U2(a,b).transpose() == U2(b+1,a+1) return get_op_ptr(OpType::U2, {params_[1] + 1., params_[0] + 1.}); @@ -367,7 +383,8 @@ std::optional Gate::is_identity() const { case OpType::YYPhase: case OpType::ZZPhase: case OpType::XXPhase3: - case OpType::ESWAP: { + case OpType::ESWAP: + case OpType::AAMS: { Expr e = params[0]; if (equiv_0(e, 4)) { return 0.; @@ -462,6 +479,22 @@ bool Gate::is_clifford() const { case OpType::PhasedISWAP: case OpType::FSim: return equiv_0(4 * params_.at(0)) && equiv_0(2 * params_.at(1)); + case OpType::GPI: + return equiv_0(8 * params_.at(0)); + case OpType::GPI2: + return equiv_0(4 * params_.at(0)); + case OpType::AAMS: + if (equiv_0(params_.at(0))) { + return true; + } else if ( + !equiv_0(4 * params_.at(0)) || !equiv_0(8 * params_.at(1)) || + !equiv_0(8 * params_.at(2))) { + return false; + } else if (equiv_0(2 * params_.at(0))) { + return true; + } else { + return equiv_0(4 * params_.at(1)) && equiv_0(4 * params_.at(2)); + } default: return false; } @@ -705,6 +738,12 @@ std::vector Gate::get_tk1_angles() const { params_.at(1) + half, params_.at(0), params_.at(2) - half, (params_.at(1) + params_.at(2)) / 2}; } + case OpType::GPI: { + return {2 * params_.at(0), 1, 0, half}; + } + case OpType::GPI2: { + return {params_.at(0), half, -params_.at(0), 0}; + } case OpType::NPhasedX: { if (n_qubits_ != 1) { throw BadOpType( diff --git a/tket/src/Gate/GateUnitaryMatrix.cpp b/tket/src/Gate/GateUnitaryMatrix.cpp index 1c4fbaef8c..2202219e83 100644 --- a/tket/src/Gate/GateUnitaryMatrix.cpp +++ b/tket/src/Gate/GateUnitaryMatrix.cpp @@ -122,6 +122,8 @@ static Eigen::MatrixXcd get_unitary_or_throw( CASE_RETURN_1P(Ry) CASE_RETURN_1P(Rz) CASE_RETURN_1P(U1) + CASE_RETURN_1P(GPI) + CASE_RETURN_1P(GPI2) CASE_RETURN_1P(CRx) CASE_RETURN_1P(CRy) CASE_RETURN_1P(CRz) @@ -140,6 +142,7 @@ static Eigen::MatrixXcd get_unitary_or_throw( #undef CASE_RETURN_2P CASE_RETURN_3P(CU3) CASE_RETURN_3P(U3) + CASE_RETURN_3P(AAMS) CASE_RETURN_3P(TK1) CASE_RETURN_3P(TK2) #undef CASE_RETURN_3P diff --git a/tket/src/Gate/GateUnitaryMatrixPrimitives.cpp b/tket/src/Gate/GateUnitaryMatrixPrimitives.cpp index 19e1d4f206..af009932ff 100644 --- a/tket/src/Gate/GateUnitaryMatrixPrimitives.cpp +++ b/tket/src/Gate/GateUnitaryMatrixPrimitives.cpp @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include +#include #include #include "tket/Gate/GateUnitaryMatrixImplementations.hpp" @@ -57,6 +59,32 @@ Eigen::Matrix2cd GateUnitaryMatrixImplementations::U1(double value) { return matr; } +Eigen::Matrix2cd GateUnitaryMatrixImplementations::GPI(double value) { + Eigen::Matrix2cd matr; + matr << 0, std::polar(1.0, -PI * value), std::polar(1.0, PI * value), 0; + return matr; +} + +Eigen::Matrix2cd GateUnitaryMatrixImplementations::GPI2(double value) { + Eigen::Matrix2cd matr; + matr << 1, -i_ * std::polar(1.0, -PI * value), + -i_ * std::polar(1.0, PI * value), 1; + return matr / std::sqrt(2.0); +} + +Eigen::Matrix4cd GateUnitaryMatrixImplementations::AAMS( + double theta, double phi0, double phi1) { + Eigen::Matrix4cd matr; + const double angle = 0.5 * PI * theta; + const double cc = cos(angle); + const double ss = sin(angle); + matr << cc, 0, 0, -i_ * std::polar(1., -PI * (phi0 + phi1)) * ss, 0, cc, + -i_ * std::polar(1., PI * (phi1 - phi0)) * ss, 0, 0, + -i_ * std::polar(1., PI * (phi0 - phi1)) * ss, cc, 0, + -i_ * std::polar(1., PI * (phi0 + phi1)) * ss, 0, 0, cc; + return matr; +} + Eigen::Matrix4cd GateUnitaryMatrixImplementations::ISWAP(double alpha) { Eigen::Matrix4cd matr = Eigen::Matrix4cd::Identity(); const double angle = 0.5 * PI * alpha; diff --git a/tket/src/OpType/OpTypeFunctions.cpp b/tket/src/OpType/OpTypeFunctions.cpp index f0796930af..adb5cae654 100644 --- a/tket/src/OpType/OpTypeFunctions.cpp +++ b/tket/src/OpType/OpTypeFunctions.cpp @@ -41,7 +41,8 @@ const OpTypeSet& all_gate_types() { OpType::CnRy, OpType::CnX, OpType::CnZ, OpType::CnY, OpType::BRIDGE, OpType::Collapse, OpType::ESWAP, OpType::FSim, OpType::Sycamore, OpType::ISWAPMax, OpType::PhasedISWAP, OpType::XXPhase3, - OpType::NPhasedX, OpType::TK2, OpType::Phase}; + OpType::NPhasedX, OpType::TK2, OpType::Phase, OpType::GPI, + OpType::GPI2, OpType::AAMS}; static std::unique_ptr gates = std::make_unique(optypes); return *gates; @@ -61,7 +62,7 @@ const OpTypeSet& all_multi_qubit_types() { OpType::CnZ, OpType::CnY, OpType::BRIDGE, OpType::ESWAP, OpType::FSim, OpType::Sycamore, OpType::ISWAPMax, OpType::PhasedISWAP, OpType::XXPhase3, - OpType::NPhasedX, OpType::TK2}; + OpType::NPhasedX, OpType::TK2, OpType::AAMS}; static std::unique_ptr gates = std::make_unique(optypes); return *gates; @@ -74,7 +75,7 @@ const OpTypeSet& all_single_qubit_unitary_types() { OpType::Sdg, OpType::T, OpType::Tdg, OpType::V, OpType::Vdg, OpType::SX, OpType::SXdg, OpType::H, OpType::Rx, OpType::Ry, OpType::Rz, OpType::U1, OpType::U2, OpType::U3, OpType::PhasedX, - OpType::TK1}; + OpType::TK1, OpType::GPI, OpType::GPI2}; static std::unique_ptr gates = std::make_unique(optypes); return *gates; @@ -87,7 +88,8 @@ const OpTypeSet& all_single_qubit_types() { OpType::Vdg, OpType::SX, OpType::SXdg, OpType::H, OpType::Rx, OpType::Ry, OpType::Rz, OpType::U3, OpType::U2, OpType::U1, OpType::TK1, OpType::Measure, - OpType::Reset, OpType::Collapse, OpType::PhasedX, OpType::noop}; + OpType::Reset, OpType::Collapse, OpType::PhasedX, OpType::noop, + OpType::GPI, OpType::GPI2}; static std::unique_ptr gates = std::make_unique(optypes); return *gates; diff --git a/tket/src/OpType/OpTypeInfo.cpp b/tket/src/OpType/OpTypeInfo.cpp index 3757f25d54..a2605edd20 100644 --- a/tket/src/OpType/OpTypeInfo.cpp +++ b/tket/src/OpType/OpTypeInfo.cpp @@ -126,6 +126,9 @@ const std::map& optypeinfo() { {OpType::CnX, {"CnX", "CnX", {}, std::nullopt}}, {OpType::CnZ, {"CnZ", "CnZ", {}, std::nullopt}}, {OpType::CnY, {"CnY", "CnY", {}, std::nullopt}}, + {OpType::GPI, {"GPI", "GPI", {2}, singleq}}, + {OpType::GPI2, {"GPI2", "GPI2", {2}, singleq}}, + {OpType::AAMS, {"AAMS", "AAMS", {4, 2, 2}, doubleq}}, {OpType::TK1, {"TK1", "TK1", {4, 4, 4}, singleq}}, {OpType::TK2, {"TK2", "TK2", {4, 4, 4}, doubleq}}, {OpType::ESWAP, {"ESWAP", "$\\mathrm{eSWAP}$", {4}, doubleq}}, diff --git a/tket/src/Predicates/CompilerPass.cpp b/tket/src/Predicates/CompilerPass.cpp index 119022b5b3..e6d3522f9d 100644 --- a/tket/src/Predicates/CompilerPass.cpp +++ b/tket/src/Predicates/CompilerPass.cpp @@ -137,13 +137,13 @@ static PredicateClassGuarantees match_class_guarantees( } PassConditions BasePass::match_passes( - const PassConditions& lhs, const PassConditions& rhs) { + const PassConditions& lhs, const PassConditions& rhs, bool strict) { PredicatePtrMap new_precons = lhs.first; for (const TypePredicatePair& precon : rhs.first) { PredicatePtrMap::const_iterator data_guar_iter = lhs.second.specific_postcons_.find(precon.first); if (data_guar_iter == lhs.second.specific_postcons_.end()) { - if (get_guarantee(precon.first, lhs) == Guarantee::Clear) { + if (strict && get_guarantee(precon.first, lhs) == Guarantee::Clear) { throw IncompatibleCompilerPasses(precon.first); } else { PredicatePtrMap::iterator new_pre_it = new_precons.find(precon.first); @@ -156,7 +156,7 @@ PassConditions BasePass::match_passes( } } } else { - if (!data_guar_iter->second->implies(*precon.second)) { + if (strict && !data_guar_iter->second->implies(*precon.second)) { throw IncompatibleCompilerPasses(precon.first); } } @@ -227,14 +227,14 @@ PassPtr operator>>(const PassPtr& lhs, const PassPtr& rhs) { return sequence; } -SequencePass::SequencePass(const std::vector& ptvec) { +SequencePass::SequencePass(const std::vector& ptvec, bool strict) { if (ptvec.size() == 0) throw std::logic_error("Cannot generate CompilerPass from empty list"); std::vector::const_iterator iter = ptvec.begin(); PassConditions conditions = (*iter)->get_conditions(); for (++iter; iter != ptvec.end(); ++iter) { const PassConditions next_cons = (*iter)->get_conditions(); - conditions = match_passes(conditions, next_cons); + conditions = match_passes(conditions, next_cons, strict); } this->precons_ = conditions.first; this->postcons_ = conditions.second; @@ -409,8 +409,6 @@ void from_json(const nlohmann::json& j, PassPtr& pp) { pp = RebaseUFR(); } else if (passname == "RemoveRedundancies") { pp = RemoveRedundancies(); - } else if (passname == "SynthesiseHQS") { - pp = SynthesiseHQS(); } else if (passname == "SynthesiseTK") { pp = SynthesiseTK(); } else if (passname == "SynthesiseTket") { diff --git a/tket/src/Predicates/PassGenerators.cpp b/tket/src/Predicates/PassGenerators.cpp index bb810d32b8..d75f48be49 100644 --- a/tket/src/Predicates/PassGenerators.cpp +++ b/tket/src/Predicates/PassGenerators.cpp @@ -32,6 +32,7 @@ #include "tket/Predicates/PassLibrary.hpp" #include "tket/Predicates/Predicates.hpp" #include "tket/Transformations/BasicOptimisation.hpp" +#include "tket/Transformations/CliffordOptimisation.hpp" #include "tket/Transformations/CliffordResynthesis.hpp" #include "tket/Transformations/ContextualReduction.hpp" #include "tket/Transformations/Decomposition.hpp" @@ -173,6 +174,23 @@ PassPtr gen_clifford_resynthesis_pass( return std::make_shared(precons, t, pc, j); } +PassPtr gen_clifford_push_through_pass() { + // Expects: Measure operations at end of circuit with + // no classical gates to outputs to work, but + // just makes no modification if this is not true + Transform t = Transforms::push_cliffords_through_measures(); + PredicatePtrMap precons; + // mutual diagonalisation circuit is not architecture aware + PredicateClassGuarantees g_postcons = { + {typeid(ConnectivityPredicate), Guarantee::Clear}, + {typeid(DirectednessPredicate), Guarantee::Clear}}; + + PostConditions pc{{}, {}, Guarantee::Preserve}; + nlohmann::json j; + j["name"] = "CliffordPushThroughMeausres"; + return std::make_shared(precons, t, pc, j); +} + PassPtr gen_flatten_relabel_registers_pass(const std::string& label) { Transform t = Transform([=](Circuit& circuit, std::shared_ptr maps) { diff --git a/tket/src/Predicates/PassLibrary.cpp b/tket/src/Predicates/PassLibrary.cpp index f013f3fe3c..e513de879f 100644 --- a/tket/src/Predicates/PassLibrary.cpp +++ b/tket/src/Predicates/PassLibrary.cpp @@ -71,12 +71,6 @@ const PassPtr &SynthesiseTket() { "SynthesiseTket")); return pp; } -const PassPtr &SynthesiseHQS() { - static const PassPtr pp(gate_translation_pass( - Transforms::synthesise_HQS(), - {OpType::ZZMax, OpType::PhasedX, OpType::Rz}, false, "SynthesiseHQS")); - return pp; -} const PassPtr &SynthesiseOQC() { static const PassPtr pp(gate_translation_pass( Transforms::synthesise_OQC(), {OpType::Rz, OpType::SX, OpType::ECR}, true, diff --git a/tket/src/Transformations/BasicOptimisation.cpp b/tket/src/Transformations/BasicOptimisation.cpp index bf4f7ffc76..6f05398af5 100644 --- a/tket/src/Transformations/BasicOptimisation.cpp +++ b/tket/src/Transformations/BasicOptimisation.cpp @@ -194,53 +194,6 @@ static bool replace_two_qubit_interaction( } } -Transform commute_and_combine_HQS2() { - return Transform([](Circuit &circ) { - bool success = false; - VertexList bin; - BGL_FORALL_VERTICES(v, circ.dag, DAG) { - EdgeVec outs = circ.get_all_out_edges(v); - if (circ.get_OpType_from_Vertex(v) == OpType::ZZMax && outs.size() == 2) { - Vertex next0 = boost::target(outs[0], circ.dag); - Vertex next1 = boost::target(outs[1], circ.dag); - if (next0 == next1 && - circ.get_OpType_from_Vertex(next0) == OpType::ZZMax) { - success = true; - EdgeVec h_in = circ.get_in_edges(v); - EdgeVec h_out = circ.get_all_out_edges(next0); - if (circ.get_target_port(outs[0]) != 0) { - h_out = {h_out[1], h_out[0]}; - } - bin.push_back(v); - bin.push_back(next0); - Subcircuit sub = {h_in, h_out}; - circ.substitute( - CircPool::two_Rz1(), sub, Circuit::VertexDeletion::No); - circ.add_phase(0.5); - continue; - } - if (circ.get_OpType_from_Vertex(next0) == OpType::Rz) { - success = true; - circ.remove_vertex( - next0, Circuit::GraphRewiring::Yes, Circuit::VertexDeletion::No); - Edge in_0 = circ.get_nth_in_edge(v, 0); - circ.rewire(next0, {in_0}, {EdgeType::Quantum}); - } - if (circ.get_OpType_from_Vertex(next1) == OpType::Rz) { - success = true; - circ.remove_vertex( - next1, Circuit::GraphRewiring::Yes, Circuit::VertexDeletion::No); - Edge in_1 = circ.get_nth_in_edge(v, 1); - circ.rewire(next1, {in_1}, {EdgeType::Quantum}); - } - } - } - circ.remove_vertices( - bin, Circuit::GraphRewiring::No, Circuit::VertexDeletion::Yes); - return success; - }); -} - Transform two_qubit_squash(bool allow_swaps) { return two_qubit_squash(OpType::CX, 1., allow_swaps); } diff --git a/tket/src/Transformations/CliffordOptimisation.cpp b/tket/src/Transformations/CliffordOptimisation.cpp index c669d3603e..ae56d04b46 100644 --- a/tket/src/Transformations/CliffordOptimisation.cpp +++ b/tket/src/Transformations/CliffordOptimisation.cpp @@ -17,10 +17,17 @@ #include #include "tket/Circuit/CircPool.hpp" +#include "tket/Circuit/Circuit.hpp" #include "tket/Circuit/DAGDefs.hpp" +#include "tket/Clifford/UnitaryTableau.hpp" +#include "tket/Converters/Converters.hpp" +#include "tket/Diagonalisation/Diagonalisation.hpp" +#include "tket/Ops/ClassicalOps.hpp" #include "tket/Transformations/BasicOptimisation.hpp" #include "tket/Transformations/Decomposition.hpp" #include "tket/Transformations/Transform.hpp" +#include "tket/Utils/PauliTensor.hpp" +#include "tket/Utils/UnitID.hpp" namespace tket { @@ -699,6 +706,251 @@ Transform singleq_clifford_sweep() { }); } +struct MeasureVertices { + Qubit qubit; + Bit bit; + Vertex measure; + MeasureVertices(const Qubit &q, const Bit &b, const Vertex &v) + : qubit(q), bit(b), measure(v) {} +}; + +std::pair> get_end_of_circuit_clifford( + const Circuit &circ) { + // Initialize vector to store MeasureVertices for end-of-circuit Measures + std::vector end_of_circuit_measures; + + // Iterate over Qubit boundaries to find Measure gates + for (auto [it, end] = + circ.boundary.get().equal_range(UnitType::Qubit); + it != end; it++) { + Vertex q_out = it->out_; + Edge last_gate_out_edge = circ.get_nth_in_edge(q_out, 0); + Vertex last_gate = circ.source(last_gate_out_edge); + + // Check if last gate is a Measure gate + if (circ.get_OpType_from_Vertex(last_gate) == OpType::Measure) { + Edge possible_c_out_in_edge = circ.get_nth_out_edge(last_gate, 1); + Vertex possible_c_out = circ.target(possible_c_out_in_edge); + + // If the Measure is followed by a ClOutput, store the MeasureVertices + if (circ.get_OpType_from_Vertex(possible_c_out) == OpType::ClOutput) { + Bit b(circ.get_id_from_out(possible_c_out)); + end_of_circuit_measures.push_back( + MeasureVertices(Qubit(it->id_), b, last_gate)); + } + } + } + + // Initialize variables for constructing Clifford circuit + VertexSet clifford_vertices; + std::map frontier; + VertexSet previous; + + // Populate frontier map with Measure edges and associated qubits + for (const auto &mv : end_of_circuit_measures) { + frontier.insert({circ.get_nth_in_edge(mv.measure, 0), mv.qubit}); + } + + // Start adding Clifford vertices to set of vertices + std::vector>> clifford_commands; + std::vector to_erase; + while (!frontier.empty()) { + VertexSet current; + // Iterate over frontier edges to identify Clifford gates + for (const auto &e : frontier) { + Vertex source = circ.source(e.first); + OpDesc desc = circ.get_OpDesc_from_Vertex(source); + // Check if gate is a Clifford gate without classical or boolean inputs + if (desc.is_clifford_gate() && desc.n_boolean() == 0 && + desc.n_classical() == 0) { + current.insert(source); + } else { + // Remove non-Clifford gates from frontier + to_erase.push_back(e.first); + } + } + for (const Edge &e : to_erase) { + frontier.erase(e); + } + // Check if identical to previous vertices + if (current == previous) { + break; + } + // Update previous set with current set + previous = current; + + // Process Clifford gates and update frontier + for (const Vertex &v : current) { + EdgeVec out_edges = circ.get_all_out_edges(v); + std::vector edges; + std::vector qubits; + + // Check which edges connect to existing qubits in the frontier + for (const Edge &edge : out_edges) { + auto it = frontier.find(edge); + if (it != frontier.end()) { + edges.push_back(it->first); + qubits.push_back(it->second); + } + } + + // If all outgoing edges connect to frontier qubits, add gate to Clifford + // circuit + if (qubits.size() == out_edges.size()) { + clifford_vertices.insert(v); + EdgeVec in_edges = circ.get_in_edges(v); + + // Update frontier with incoming edges to processed gate + for (unsigned i = 0; i < edges.size(); i++) { + Edge e = edges[i]; + port_t source = circ.get_source_port(e); + frontier.insert({in_edges[source], qubits[i]}); + frontier.erase(e); + } + } + } + } + return std::make_pair(clifford_vertices, end_of_circuit_measures); +} + +Transform push_cliffords_through_measures() { + return Transform([](Circuit &circ) { + // Extract Clifford and Measure vertices information from provided circuit + std::pair> clifford_info = + get_end_of_circuit_clifford(circ); + VertexSet clifford_vertices = clifford_info.first; + std::vector measure_vertices = clifford_info.second; + + // Initialize vectors to track various information + std::vector qubits; + std::vector bits; + QubitPauliMap base; + VertexSet final_measure_vertices; + + // Populate qubits, bits, basic measurement operators and final measurement + // vertices + for (const MeasureVertices &mv : measure_vertices) { + qubits.push_back(mv.qubit); + bits.push_back(mv.bit); + base.insert({mv.qubit, Pauli::I}); + final_measure_vertices.insert(mv.measure); + } + + // Construct a circuit with Clifford gates + Circuit clifford_circuit = + circ.subcircuit(circ.make_subcircuit(clifford_vertices)); + UnitaryRevTableau cliff_tab = + circuit_to_unitary_rev_tableau(clifford_circuit); + + // Generate updated measurement operator for each end of circuit measurement + std::list measurement_operators; + for (const Qubit &q : qubits) { + QubitPauliMap copy = base; + copy[q] = Pauli::Z; + SpPauliStabiliser sps(copy, 2); + measurement_operators.push_back(cliff_tab.get_row_product(sps)); + } + + // Mutually diagonalize updated measuement operators + Circuit mutual_c = mutual_diagonalise( + measurement_operators, std::set(qubits.begin(), qubits.end()), + CXConfigType::Snake); + + // If mutual diagonalisation circuit doesn't improve 2-qubit gate + // count then keep circuit as is + if (mutual_c.count_n_qubit_gates(2) >= + clifford_circuit.count_n_qubit_gates(2)) { + return false; + } + + // Remove Clifford and Measure vertices from the original circuit + // before adding mutual diagonalisation circuit + circ.remove_vertices( + clifford_vertices, Circuit::GraphRewiring::Yes, + Circuit::VertexDeletion::Yes); + circ.remove_vertices( + final_measure_vertices, Circuit::GraphRewiring::Yes, + Circuit::VertexDeletion::Yes); + // add mutual diagonalisation circuit + circ.append(mutual_c); + + // add back in end of circuit measurements + TKET_ASSERT(qubits.size() == bits.size()); + for (unsigned i = 0; i < qubits.size(); i++) { + circ.add_measure(qubits[i], bits[i]); + } + + // Add classical logic to permute output measurements to correct result + std::string reg_name = + circ.get_next_c_reg_name(c_permutation_scratch_name()); + register_t scratch_r = circ.add_c_register(reg_name, bits.size() + 1); + // Convert Bit to vector for ease of indexing and assigning + std::vector scratch_v; + for (const auto &b : scratch_r) { + Bit scratch_b = Bit(b.second); + scratch_v.push_back(scratch_b); + } + + // We need to collect ClassicalX due to phase correction + // and permutation due to Z terms in operator + std::vector phase_correction; + + TKET_ASSERT(measurement_operators.size() == bits.size()); + auto it = measurement_operators.begin(); + for (unsigned i = 0; i < measurement_operators.size(); ++i, ++it) { + // For each measurement operator, we collect the qubits in the string + // which have Z terms + auto string = it->string; + std::vector parity_bits; + for (unsigned j = 0; j < qubits.size(); j++) { + Qubit q = qubits[j]; + auto jt = string.find(q); + if (jt != string.end()) { + // If the qubit has a Z term, then it's measurement result + // needs to be Xored with the original target qubit of the + // measurement + if (jt->second == Pauli::Z) { + parity_bits.push_back(bits[j]); + } else { + TKET_ASSERT(jt->second == Pauli::I); + } + } + } + // The measurement operator should never be empty, meaning + // that parity_bits should always have at least one entry + TKET_ASSERT(!parity_bits.empty()); + // we now add the Xor corresponding to the correction + for (const Bit &pb : parity_bits) { + std::vector arg = {pb, Bit(scratch_v[i])}; + circ.add_op(XorWithOp(), arg); + } + + // Finally, we check the phase, and store the required Bit + // to flip as appropriate + if (it->coeff == 1) { + phase_correction.push_back(scratch_v[i]); + } else { + TKET_ASSERT(it->coeff == -1); + } + } + + // Apply the constant change to bitstring due to phase + circ.add_op( + std::make_shared(std::vector({true})), + std::vector({scratch_v.back()})); + for (const auto &p_bit : phase_correction) { + circ.add_op(XorWithOp(), std::vector({scratch_v.back(), p_bit})); + } + + // Copy scratch results over to original Bit + TKET_ASSERT(bits.size() + 1 == scratch_v.size()); + scratch_v.pop_back(); + scratch_v.insert(scratch_v.end(), bits.begin(), bits.end()); + circ.add_op(std::make_shared(unsigned(bits.size())), scratch_v); + return true; + }); +} + } // namespace Transforms } // namespace tket diff --git a/tket/src/Transformations/MeasurePass.cpp b/tket/src/Transformations/MeasurePass.cpp index 2119bcd275..30c8861c8c 100644 --- a/tket/src/Transformations/MeasurePass.cpp +++ b/tket/src/Transformations/MeasurePass.cpp @@ -105,7 +105,7 @@ static Edge follow_until_noncommuting( * through SWAPs, gates that commute on the Pauli Z basis, and recursively * checks inside boxes and conditional operations. * @param circ The circuit containing the operation. - * @param v The vertex of the operation. + * @param op The operation. * @param in_port The input port of the operation to check. * @param only_boxes Whether to only commute through straight wires in boxes or * conditional operations. @@ -191,9 +191,12 @@ std::pair run_delay_measures( Vertex v = com.get_vertex(); OpType optype = com.get_op_ptr()->get_type(); if (optype == OpType::Measure) { - if (!allow_partial) { - Edge c_out_edge = circ.get_nth_out_edge(v, 1); - if (!circ.detect_final_Op(circ.target(c_out_edge)) || + Edge c_out_edge = circ.get_nth_out_edge(v, 1); + bool measurement_is_final = circ.detect_final_Op(circ.target(c_out_edge)); + if (allow_partial) { + if (!measurement_is_final) continue; + } else { + if (!measurement_is_final || circ.n_out_edges_of_type(v, EdgeType::Boolean) != 0) { if (dry_run) return {false, false}; throw CircuitInvalidity( diff --git a/tket/src/Transformations/OptimisationPass.cpp b/tket/src/Transformations/OptimisationPass.cpp index c0ce1aa4bd..8491099c69 100644 --- a/tket/src/Transformations/OptimisationPass.cpp +++ b/tket/src/Transformations/OptimisationPass.cpp @@ -138,21 +138,6 @@ Transform synthesise_OQC() { }); } -/* Returns a Circuit with only HQS allowed Ops (Rz, PhasedX, ZZMax) */ -Transform synthesise_HQS() { - return Transform([](Circuit &circ) { - Transform single_loop = - remove_redundancies() >> commute_through_multis() >> reduce_XZ_chains(); - Transform hqs_loop = remove_redundancies() >> commute_and_combine_HQS2() >> - reduce_XZ_chains(); - Transform main_seq = - decompose_multi_qubits_CX() >> clifford_simp() >> decompose_ZX() >> - repeat(single_loop) >> decompose_CX_to_HQS2() >> repeat(hqs_loop) >> - decompose_ZX_to_HQS1() >> rebase_HQS() >> remove_redundancies(); - return main_seq.apply(circ); - }); -} - // TODO: Make the XXPhase gates combine Transform synthesise_UMD() { return Transform([](Circuit &circ) { diff --git a/tket/src/Transformations/PQPSquash.cpp b/tket/src/Transformations/PQPSquash.cpp index 8eb89b323b..68cd73c2fe 100644 --- a/tket/src/Transformations/PQPSquash.cpp +++ b/tket/src/Transformations/PQPSquash.cpp @@ -187,12 +187,6 @@ static bool squash_to_pqp( return SingleQubitSquash(std::move(squasher), circ, reverse).squash(); } -Transform reduce_XZ_chains() { - return Transform([](Circuit &circ) { - return squash_to_pqp(circ, OpType::Rx, OpType::Rz); - }); -} - Transform squash_1qb_to_pqp(const OpType &q, const OpType &p, bool strict) { return Transform( [=](Circuit &circ) { return squash_to_pqp(circ, q, p, strict); }); diff --git a/tket/src/Transformations/PauliOptimisation.cpp b/tket/src/Transformations/PauliOptimisation.cpp index 928fa73c58..eac7732d04 100644 --- a/tket/src/Transformations/PauliOptimisation.cpp +++ b/tket/src/Transformations/PauliOptimisation.cpp @@ -14,8 +14,8 @@ #include "tket/Transformations/PauliOptimisation.hpp" +#include "tket/Circuit/CircUtils.hpp" #include "tket/Converters/Converters.hpp" -#include "tket/Converters/PauliGadget.hpp" #include "tket/OpType/OpType.hpp" #include "tket/OpType/OpTypeInfo.hpp" #include "tket/Ops/Op.hpp" @@ -168,14 +168,14 @@ Transform pairwise_pauli_gadgets(CXConfigType cx_config) { // Synthesise pairs of Pauli Gadgets unsigned g = 0; while (g + 1 < pauli_gadgets.size()) { - append_pauli_gadget_pair( - gadget_circ, pauli_gadgets[g], pauli_gadgets[g + 1], cx_config); + gadget_circ.append( + pauli_gadget_pair(pauli_gadgets[g], pauli_gadgets[g + 1], cx_config)); g += 2; } // As we synthesised Pauli gadgets 2 at a time, if there were an odd // number, we will have one left over, so add that one on its own if (g < pauli_gadgets.size()) { - append_single_pauli_gadget(gadget_circ, pauli_gadgets[g], cx_config); + gadget_circ.append(pauli_gadget(pauli_gadgets[g], cx_config)); } // Stitch gadget circuit and Clifford circuit together circ = gadget_circ >> clifford_circ; diff --git a/tket/src/Transformations/PhasedXFrontier.cpp b/tket/src/Transformations/PhasedXFrontier.cpp index 78a175b59b..5829d18042 100644 --- a/tket/src/Transformations/PhasedXFrontier.cpp +++ b/tket/src/Transformations/PhasedXFrontier.cpp @@ -171,6 +171,13 @@ Edge PhasedXFrontier::get_interval_start(Edge e) const { bool PhasedXFrontier::is_interval_boundary(Op_ptr op) { OpType type = op->get_type(); + if (type == OpType::Conditional) { + // We currently split intervals on Conditional gates even if they + // are single-qubit gates, as there is no simple way to guarantee that + // classically controlled gates are space-like separated without making + // additional assumptions or walking the DAG. + return true; + } return is_gate_type(type) && as_gate_ptr(op)->n_qubits() > 1 && type != OpType::NPhasedX; } @@ -437,6 +444,20 @@ bool all_nullopt(const OptVertexVec& vec) { return true; } +Vertex PhasedXFrontierTester::get_interval_start(unsigned i) { + auto edge = frontier_.intervals_[i].first; + return circ_.source(edge); +} + +Vertex PhasedXFrontierTester::get_interval_end(unsigned i) { + auto edge = frontier_.intervals_[i].second; + return circ_.target(edge); +} + +PhasedXFrontierTester::PhasedXFrontierTester( + PhasedXFrontier& frontier, Circuit& circ) + : frontier_(frontier), circ_(circ) {} + } // namespace Transforms } // namespace tket diff --git a/tket/src/Transformations/RedundancyRemoval.cpp b/tket/src/Transformations/RedundancyRemoval.cpp index b3e83fbeb9..c026aa1b40 100644 --- a/tket/src/Transformations/RedundancyRemoval.cpp +++ b/tket/src/Transformations/RedundancyRemoval.cpp @@ -214,10 +214,12 @@ static bool is_apriori_not_detachable( const OpDesc op_descriptor = circuit.get_Op_ptr_from_Vertex(vertex)->get_desc(); - return (not op_descriptor.is_gate()) or // not a gate - circuit.n_out_edges(vertex) == - 0 or // vertex is boundary or already detached - circuit.n_in_edges(vertex) == 0; // vertex is boundary + if (!op_descriptor.is_gate()) return true; + if (op_descriptor.type() == OpType::Phase) return false; + if (circuit.n_out_edges(vertex) == 0 || circuit.n_in_edges(vertex) == 0) { + return true; // vertex is boundary or already detached + } + return false; } static bool try_detach_vertex( diff --git a/tket/src/Utils/UnitID.cpp b/tket/src/Utils/UnitID.cpp index bdfe0a6249..773a1db178 100644 --- a/tket/src/Utils/UnitID.cpp +++ b/tket/src/Utils/UnitID.cpp @@ -117,4 +117,10 @@ const std::string& c_debug_default_name() { return *regname; } +const std::string& c_permutation_scratch_name() { + static std::unique_ptr regname = + std::make_unique("permutation_scratch"); + return *regname; +} + } // namespace tket diff --git a/tket/test/CMakeLists.txt b/tket/test/CMakeLists.txt index 5b1ccb620d..65cf8824ef 100644 --- a/tket/test/CMakeLists.txt +++ b/tket/test/CMakeLists.txt @@ -124,6 +124,7 @@ add_executable(test-tket src/Circuit/test_DummyBox.cpp src/test_UnitaryTableau.cpp src/test_ChoiMixTableau.cpp + src/test_Diagonalisation.cpp src/test_PhasePolynomials.cpp src/test_PauliGraph.cpp src/test_Architectures.cpp diff --git a/tket/test/src/Circuit/test_Boxes.cpp b/tket/test/src/Circuit/test_Boxes.cpp index 3d11e900cd..bfaed1f186 100644 --- a/tket/test/src/Circuit/test_Boxes.cpp +++ b/tket/test/src/Circuit/test_Boxes.cpp @@ -119,6 +119,10 @@ SCENARIO("Using Boxes", "[boxes]") { Eigen::MatrixXcd uc0 = tket_sim::get_unitary(c0); Eigen::MatrixXcd uc0a = tket_sim::get_unitary(c0a); REQUIRE((uc0 - uc0a).cwiseAbs().sum() < ERR_EPS); + // Test circuit name access + CircBox cbox(Circuit(2)); + cbox.set_circuit_name("test_box"); + REQUIRE(cbox.get_circuit_name() == "test_box"); } GIVEN("CircBox with non-default units") { Circuit c0; diff --git a/tket/test/src/Circuit/test_Circ.cpp b/tket/test/src/Circuit/test_Circ.cpp index ef4e42acdf..a59d77692e 100644 --- a/tket/test/src/Circuit/test_Circ.cpp +++ b/tket/test/src/Circuit/test_Circ.cpp @@ -2800,11 +2800,20 @@ SCENARIO("(qu)bit_readout/mapping for a circuit") { REQUIRE(qb_map.at(Qubit(qreg[2])) == creg[2]); } -SCENARIO("Trying to add a Measure gate with no classical output") { - Circuit circ(2); - circ.add_op(OpType::CX, {0, 1}); - REQUIRE_THROWS_AS( - circ.add_op(OpType::Measure, {0}), CircuitInvalidity); +SCENARIO("Invalid Measure operations") { + GIVEN("Trying to add a Measure gate with no classical output") { + Circuit circ(2); + circ.add_op(OpType::CX, {0, 1}); + REQUIRE_THROWS_AS( + circ.add_op(OpType::Measure, {0}), CircuitInvalidity); + } + GIVEN("Trying to add a Measure on non-existent wires") { + // https://github.com/CQCL/tket/issues/979 + Circuit circ; + REQUIRE_THROWS(circ.add_measure(0, 0)); + std::vector cmds = circ.get_commands(); + REQUIRE(cmds.empty()); + } } SCENARIO("Testing add_op with Barrier type and add_barrier") { diff --git a/tket/test/src/Graphs/test_ArticulationPoints.cpp b/tket/test/src/Graphs/test_ArticulationPoints.cpp index 86d1bd7ff8..394e867870 100644 --- a/tket/test/src/Graphs/test_ArticulationPoints.cpp +++ b/tket/test/src/Graphs/test_ArticulationPoints.cpp @@ -145,7 +145,8 @@ SCENARIO("Find APs of disconnected nodes") { } SCENARIO("Test APs in Architecture", "[architectures],[routing]") { - Architecture arc({{0, 1}, {1, 2}, {2, 3}}); + Architecture arc( + {{Node(0), Node(1)}, {Node(1), Node(2)}, {Node(2), Node(3)}}); node_set_t ap = arc.get_articulation_points(); GIVEN("Removing node that preserves connected graph") { @@ -157,7 +158,11 @@ SCENARIO("Test APs in Architecture", "[architectures],[routing]") { REQUIRE(ap.find(Node(2)) != ap.end()); } - Architecture arc2({{0, 1}, {0, 2}, {0, 3}, {2, 3}}); + Architecture arc2( + {{Node(0), Node(1)}, + {Node(0), Node(2)}, + {Node(0), Node(3)}, + {Node(2), Node(3)}}); ap = arc2.get_articulation_points(); REQUIRE(ap.find(Node(0)) != ap.end()); diff --git a/tket/test/src/test_Architectures.cpp b/tket/test/src/test_Architectures.cpp index 761aba5043..b22dc47fc4 100644 --- a/tket/test/src/test_Architectures.cpp +++ b/tket/test/src/test_Architectures.cpp @@ -123,12 +123,20 @@ SCENARIO("Diameters") { CHECK(arc.get_diameter() == 0); } GIVEN("a connected architecture") { - Architecture arc({{0, 1}, {1, 2}, {2, 3}, {3, 0}}); + Architecture arc( + {{Node(0), Node(1)}, + {Node(1), Node(2)}, + {Node(2), Node(3)}, + {Node(3), Node(0)}}); CHECK(arc.get_diameter() == 2); } GIVEN("a disconnected architecture") { // TKET-1425 - Architecture arc({{0, 1}, {1, 2}, {2, 0}, {3, 4}}); + Architecture arc( + {{Node(0), Node(1)}, + {Node(1), Node(2)}, + {Node(2), Node(0)}, + {Node(3), Node(4)}}); CHECK_THROWS(arc.get_diameter()); } } diff --git a/tket/test/src/test_ChoiMixTableau.cpp b/tket/test/src/test_ChoiMixTableau.cpp index 49ad200d54..2e5441c3b9 100644 --- a/tket/test/src/test_ChoiMixTableau.cpp +++ b/tket/test/src/test_ChoiMixTableau.cpp @@ -15,6 +15,7 @@ #include #include "testutil.hpp" +#include "tket/Circuit/Simulation/CircuitSimulator.hpp" #include "tket/Converters/Converters.hpp" namespace tket { @@ -60,6 +61,12 @@ static ChoiMixTableau get_tableau_with_gates_applied_at_front() { OpType::CX, {Qubit(0), Qubit(1)}, ChoiMixTableau::TableauSegment::Input); return tab; } +static qubit_map_t inv_perm(const qubit_map_t& perm) { + qubit_map_t inv; + for (const std::pair& qp : perm) + inv.insert({qp.second, qp.first}); + return inv; +} SCENARIO("Correct creation of ChoiMixTableau") { GIVEN( @@ -89,7 +96,7 @@ SCENARIO("Correct creation of ChoiMixTableau") { tab.get_row_product({0, 1}) == ChoiMixTableau::row_tensor_t{ SpPauliStabiliser(Qubit(0), Pauli::Y), - SpPauliStabiliser(Qubit(0), Pauli::Y, 2)}); + SpPauliStabiliser(Qubit(0), Pauli::Y)}); THEN("Serialize and deserialize") { nlohmann::json j_tab = tab; ChoiMixTableau tab2{{}}; @@ -174,12 +181,10 @@ SCENARIO("Correct creation of ChoiMixTableau") { SpPauliStabiliser(Qubit(0), Pauli::Z), SpPauliStabiliser(Qubit(0), Pauli::Z)}); // Affecting the input segment should give the same effect as for - // UnitaryRevTableau (since lhs is transposed, +Y is flipped to -Y, and - // phase is returned on rhs) + // UnitaryRevTableau REQUIRE( - tab.get_row(2) == - ChoiMixTableau::row_tensor_t{ - SpPauliStabiliser(Qubit(1), Pauli::Y), SpPauliStabiliser({}, 2)}); + tab.get_row(2) == ChoiMixTableau::row_tensor_t{ + SpPauliStabiliser(Qubit(1), Pauli::Y), {}}); // Affecting the output segment should give the same effect as for // UnitaryTableau REQUIRE( @@ -197,9 +202,8 @@ SCENARIO("Correct creation of ChoiMixTableau") { SpPauliStabiliser(Qubit(0), Pauli::Z), SpPauliStabiliser(Qubit(0), Pauli::Y, 2)}); REQUIRE( - tab.get_row(2) == - ChoiMixTableau::row_tensor_t{ - SpPauliStabiliser(Qubit(1), Pauli::Y), SpPauliStabiliser({}, 2)}); + tab.get_row(2) == ChoiMixTableau::row_tensor_t{ + SpPauliStabiliser(Qubit(1), Pauli::Y), {}}); REQUIRE( tab.get_row(3) == ChoiMixTableau::row_tensor_t{ {}, SpPauliStabiliser(Qubit(2), Pauli::Y, 2)}); @@ -215,9 +219,8 @@ SCENARIO("Correct creation of ChoiMixTableau") { SpPauliStabiliser(Qubit(0), Pauli::Z), SpPauliStabiliser(Qubit(0), Pauli::Z, 2)}); REQUIRE( - tab.get_row(2) == - ChoiMixTableau::row_tensor_t{ - SpPauliStabiliser(Qubit(1), Pauli::Y), SpPauliStabiliser({}, 2)}); + tab.get_row(2) == ChoiMixTableau::row_tensor_t{ + SpPauliStabiliser(Qubit(1), Pauli::Y), {}}); REQUIRE( tab.get_row(3) == ChoiMixTableau::row_tensor_t{ {}, SpPauliStabiliser(Qubit(2), Pauli::Y, 2)}); @@ -449,17 +452,21 @@ SCENARIO("Synthesis of circuits from ChoiMixTableaus") { GIVEN("An identity circuit") { Circuit circ(3); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - Circuit res = cm_tableau_to_circuit(tab).first; + Circuit res = cm_tableau_to_exact_circuit(tab).first; + REQUIRE(res == circ); + res = cm_tableau_to_unitary_extension_circuit(tab).first; REQUIRE(res == circ); } GIVEN("Just some Pauli gates for phase tests") { Circuit circ(4); circ.add_op(OpType::X, {1}); - circ.add_op(OpType::X, {2}); circ.add_op(OpType::Z, {2}); + circ.add_op(OpType::X, {2}); circ.add_op(OpType::Z, {3}); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - Circuit res = cm_tableau_to_circuit(tab).first; + Circuit res = cm_tableau_to_exact_circuit(tab).first; + REQUIRE(res == circ); + res = cm_tableau_to_unitary_extension_circuit(tab).first; REQUIRE(res == circ); } GIVEN("Iterate through single-qubit Cliffords with all entanglements") { @@ -480,7 +487,9 @@ SCENARIO("Synthesis of circuits from ChoiMixTableaus") { if ((i / 9) % 3 == 1) circ.add_op(OpType::S, {0}); if ((i / 9) % 3 == 2) circ.add_op(OpType::Sdg, {0}); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - Circuit res = cm_tableau_to_circuit(tab).first; + Circuit res = cm_tableau_to_exact_circuit(tab).first; + Circuit res_uni = cm_tableau_to_unitary_extension_circuit(tab).first; + REQUIRE(res == res_uni); ChoiMixTableau res_tab = circuit_to_cm_tableau(res); REQUIRE(res_tab == tab); REQUIRE(test_unitary_comparison(circ, res, true)); @@ -489,10 +498,15 @@ SCENARIO("Synthesis of circuits from ChoiMixTableaus") { GIVEN("A unitary circuit") { Circuit circ = get_test_circ(); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - Circuit res = cm_tableau_to_circuit(tab).first; - ChoiMixTableau res_tab = circuit_to_cm_tableau(res); + std::pair res = cm_tableau_to_exact_circuit(tab); + res.first.permute_boundary_output(inv_perm(res.second)); + std::pair res_uni = + cm_tableau_to_unitary_extension_circuit(tab); + res_uni.first.permute_boundary_output(inv_perm(res_uni.second)); + REQUIRE(res.first == res_uni.first); + ChoiMixTableau res_tab = circuit_to_cm_tableau(res.first); REQUIRE(res_tab == tab); - REQUIRE(test_unitary_comparison(circ, res, true)); + REQUIRE(test_unitary_comparison(circ, res.first, true)); } GIVEN("Check unitary equivalence by calculating matrix") { Circuit circ(4); @@ -501,21 +515,69 @@ SCENARIO("Synthesis of circuits from ChoiMixTableaus") { circ.add_op(OpType::ISWAPMax, {0, 3}); circ.add_op(OpType::SX, {1}); circ.add_op(OpType::SXdg, {2}); + circ.add_op(OpType::CY, {1, 3}); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - Circuit res = cm_tableau_to_circuit(tab).first; - REQUIRE(test_unitary_comparison(circ, res, true)); + std::pair res = cm_tableau_to_exact_circuit(tab); + res.first.permute_boundary_output(inv_perm(res.second)); + std::pair res_uni = + cm_tableau_to_unitary_extension_circuit(tab); + res_uni.first.permute_boundary_output(inv_perm(res_uni.second)); + REQUIRE(res.first == res_uni.first); + REQUIRE(test_unitary_comparison(circ, res.first, true)); + THEN("Build the tableau manually for apply_gate coverage on inputs") { + ChoiMixTableau rev_tab(4); + rev_tab.apply_gate( + OpType::CY, {Qubit(1), Qubit(3)}, + ChoiMixTableau::TableauSegment::Input); + rev_tab.apply_gate( + OpType::SXdg, {Qubit(2)}, ChoiMixTableau::TableauSegment::Input); + rev_tab.apply_gate( + OpType::SX, {Qubit(1)}, ChoiMixTableau::TableauSegment::Input); + rev_tab.apply_gate( + OpType::ISWAPMax, {Qubit(0), Qubit(3)}, + ChoiMixTableau::TableauSegment::Input); + rev_tab.apply_gate( + OpType::ECR, {Qubit(2), Qubit(3)}, + ChoiMixTableau::TableauSegment::Input); + rev_tab.apply_gate( + OpType::ZZMax, {Qubit(0), Qubit(1)}, + ChoiMixTableau::TableauSegment::Input); + rev_tab.canonical_column_order(); + rev_tab.gaussian_form(); + REQUIRE(tab == rev_tab); + } } GIVEN("A Clifford state") { Circuit circ = get_test_circ(); circ.qubit_create_all(); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - Circuit res = cm_tableau_to_circuit(tab).first; + Circuit res = cm_tableau_to_exact_circuit(tab).first; + ChoiMixTableau res_tab = circuit_to_cm_tableau(res); + REQUIRE(res_tab == tab); + Circuit res_uni = + cm_tableau_to_unitary_extension_circuit(tab, circ.all_qubits()).first; + REQUIRE(test_statevector_comparison(res, res_uni, true)); + } + GIVEN("A partial Clifford state (tests mixed initialisations)") { + Circuit circ(3); + add_ops_list_one_to_circuit(circ); + circ.add_op(OpType::Collapse, {1}); + circ.qubit_create_all(); + ChoiMixTableau tab = circuit_to_cm_tableau(circ); + Circuit res = cm_tableau_to_exact_circuit(tab).first; + CHECK(res.created_qubits().size() == 3); + CHECK(res.discarded_qubits().size() == 0); + CHECK(res.count_gates(OpType::Collapse) == 1); ChoiMixTableau res_tab = circuit_to_cm_tableau(res); - tab.canonical_column_order(); - tab.gaussian_form(); - res_tab.canonical_column_order(); - res_tab.gaussian_form(); REQUIRE(res_tab == tab); + Circuit res_uni = + cm_tableau_to_unitary_extension_circuit(tab, circ.all_qubits()).first; + Eigen::VectorXcd res_sv = tket_sim::get_statevector(res_uni); + for (unsigned r = 0; r < tab.get_n_rows(); ++r) { + ChoiMixTableau::row_tensor_t rrow = tab.get_row(r); + Eigen::MatrixXcd outmat = rrow.second.to_sparse_matrix(3); + CHECK((outmat * res_sv).isApprox(res_sv)); + } } GIVEN("A total diagonalisation circuit") { Circuit circ = get_test_circ(); @@ -523,9 +585,14 @@ SCENARIO("Synthesis of circuits from ChoiMixTableaus") { circ.add_op(OpType::Collapse, {i}); } ChoiMixTableau tab = circuit_to_cm_tableau(circ); - Circuit res = cm_tableau_to_circuit(tab).first; + Circuit res = cm_tableau_to_exact_circuit(tab).first; ChoiMixTableau res_tab = circuit_to_cm_tableau(res); REQUIRE(res_tab == tab); + // Test unitary synthesis by statevector of dagger + Circuit as_state = get_test_circ().dagger(); + Circuit res_uni_dag = + cm_tableau_to_unitary_extension_circuit(tab).first.dagger(); + REQUIRE(test_statevector_comparison(as_state, res_uni_dag, true)); } GIVEN("A partial diagonalisation circuit") { Circuit circ = get_test_circ(); @@ -534,18 +601,26 @@ SCENARIO("Synthesis of circuits from ChoiMixTableaus") { } circ.qubit_discard(Qubit(0)); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - std::pair res = cm_tableau_to_circuit(tab); + std::pair res = cm_tableau_to_exact_circuit(tab); ChoiMixTableau res_tab = circuit_to_cm_tableau(res.first); qubit_map_t perm; - for (const std::pair& p : res.second) { - perm.insert({Qubit(p.second), Qubit(p.first)}); + for (const std::pair& p : res.second) { + perm.insert({p.second, p.first}); } res_tab.rename_qubits(perm, ChoiMixTableau::TableauSegment::Output); - tab.canonical_column_order(); - tab.gaussian_form(); res_tab.canonical_column_order(); res_tab.gaussian_form(); REQUIRE(res_tab == tab); + Circuit res_uni_dag = + cm_tableau_to_unitary_extension_circuit(tab).first.dagger(); + Eigen::VectorXcd as_state = tket_sim::get_statevector(res_uni_dag); + for (unsigned r = 0; r < tab.get_n_rows(); ++r) { + ChoiMixTableau::row_tensor_t rrow = tab.get_row(r); + CmplxSpMat rmat = rrow.first.to_sparse_matrix(3); + if (rrow.second.is_real_negative()) rmat *= -1.; + Eigen::MatrixXcd rmatd = rmat; + CHECK((rmat * as_state).isApprox(as_state)); + } } GIVEN("Another circuit for extra test coverage in row reductions") { Circuit circ(5); @@ -564,13 +639,24 @@ SCENARIO("Synthesis of circuits from ChoiMixTableaus") { circ.add_op(OpType::Collapse, {4}); circ.add_op(OpType::H, {4}); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - Circuit res = cm_tableau_to_circuit(tab).first; - ChoiMixTableau res_tab = circuit_to_cm_tableau(res); - tab.canonical_column_order(); - tab.gaussian_form(); - res_tab.canonical_column_order(); - res_tab.gaussian_form(); + std::pair res = cm_tableau_to_exact_circuit(tab); + res.first.permute_boundary_output(inv_perm(res.second)); + ChoiMixTableau res_tab = circuit_to_cm_tableau(res.first); REQUIRE(res_tab == tab); + std::pair res_uni = + cm_tableau_to_unitary_extension_circuit(tab); + res_uni.first.permute_boundary_output(inv_perm(res_uni.second)); + res_tab = circuit_to_cm_tableau(res_uni.first); + res_tab.tab_.row_mult(0, 1); + Eigen::MatrixXcd res_u = tket_sim::get_unitary(res_uni.first); + for (unsigned r = 0; r < tab.get_n_rows(); ++r) { + ChoiMixTableau::row_tensor_t rrow = tab.get_row(r); + CmplxSpMat inmat = rrow.first.to_sparse_matrix(5); + Eigen::MatrixXcd inmatd = inmat; + CmplxSpMat outmat = rrow.second.to_sparse_matrix(5); + Eigen::MatrixXcd outmatd = outmat; + CHECK((outmatd * res_u * inmatd).isApprox(res_u)); + } } GIVEN("An isometry") { Circuit circ(5); @@ -586,18 +672,36 @@ SCENARIO("Synthesis of circuits from ChoiMixTableaus") { circ.add_op(OpType::CX, {1, 2}); circ.add_op(OpType::CX, {1, 0}); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - std::pair res = cm_tableau_to_circuit(tab); + std::pair res = cm_tableau_to_exact_circuit(tab); ChoiMixTableau res_tab = circuit_to_cm_tableau(res.first); qubit_map_t perm; - for (const std::pair& p : res.second) { - perm.insert({Qubit(p.second), Qubit(p.first)}); + for (const std::pair& p : res.second) { + perm.insert({p.second, p.first}); } res_tab.rename_qubits(perm, ChoiMixTableau::TableauSegment::Output); - tab.canonical_column_order(); - tab.gaussian_form(); res_tab.canonical_column_order(); res_tab.gaussian_form(); REQUIRE(res_tab == tab); + std::pair res_uni = + cm_tableau_to_unitary_extension_circuit( + tab, {Qubit(1), Qubit(2), Qubit(3)}); + Eigen::MatrixXcd res_u = tket_sim::get_unitary(res_uni.first); + Eigen::MatrixXcd init_proj = Eigen::MatrixXcd::Zero(32, 32); + init_proj.block(0, 0, 2, 2) = Eigen::MatrixXcd::Identity(2, 2); + init_proj.block(16, 16, 2, 2) = Eigen::MatrixXcd::Identity(2, 2); + Eigen::MatrixXcd res_iso = res_u * init_proj; + for (unsigned r = 0; r < tab.get_n_rows(); ++r) { + ChoiMixTableau::row_tensor_t rrow = tab.get_row(r); + CmplxSpMat inmat = rrow.first.to_sparse_matrix(5); + Eigen::MatrixXcd inmatd = inmat; + QubitPauliMap outstr; + for (const std::pair& qp : rrow.second.string) + outstr.insert({res_uni.second.at(qp.first), qp.second}); + CmplxSpMat outmat = SpPauliString(outstr).to_sparse_matrix(5); + Eigen::MatrixXcd outmatd = outmat; + if (rrow.second.is_real_negative()) outmatd *= -1.; + CHECK((outmatd * res_iso * inmatd).isApprox(res_iso)); + } } GIVEN("Extra coverage for isometries") { Circuit circ(5); @@ -615,18 +719,215 @@ SCENARIO("Synthesis of circuits from ChoiMixTableaus") { circ.add_op(OpType::CX, {1, 2}); circ.add_op(OpType::CX, {1, 0}); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - std::pair res = cm_tableau_to_circuit(tab); + std::pair res = cm_tableau_to_exact_circuit(tab); ChoiMixTableau res_tab = circuit_to_cm_tableau(res.first); qubit_map_t perm; - for (const std::pair& p : res.second) { - perm.insert({Qubit(p.second), Qubit(p.first)}); + for (const std::pair& p : res.second) { + perm.insert({p.second, p.first}); } res_tab.rename_qubits(perm, ChoiMixTableau::TableauSegment::Output); + res_tab.canonical_column_order(); + res_tab.gaussian_form(); + REQUIRE(res_tab == tab); + std::pair res_uni = + cm_tableau_to_unitary_extension_circuit( + tab, {Qubit(1), Qubit(2), Qubit(3)}); + Eigen::MatrixXcd res_u = tket_sim::get_unitary(res_uni.first); + Eigen::MatrixXcd init_proj = Eigen::MatrixXcd::Zero(32, 32); + init_proj.block(0, 0, 2, 2) = Eigen::MatrixXcd::Identity(2, 2); + init_proj.block(16, 16, 2, 2) = Eigen::MatrixXcd::Identity(2, 2); + Eigen::MatrixXcd res_iso = res_u * init_proj; + for (unsigned r = 0; r < tab.get_n_rows(); ++r) { + ChoiMixTableau::row_tensor_t rrow = tab.get_row(r); + CmplxSpMat inmat = rrow.first.to_sparse_matrix(5); + Eigen::MatrixXcd inmatd = inmat; + QubitPauliMap outstr; + for (const std::pair& qp : rrow.second.string) + outstr.insert({res_uni.second.at(qp.first), qp.second}); + CmplxSpMat outmat = SpPauliString(outstr).to_sparse_matrix(5); + Eigen::MatrixXcd outmatd = outmat; + if (rrow.second.is_real_negative()) outmatd *= -1.; + CHECK((outmatd * res_iso * inmatd).isApprox(res_iso)); + } + } + GIVEN("Synthesising a tableau requiring post-selection") { + Circuit circ = get_test_circ(); + ChoiMixTableau tab = circuit_to_cm_tableau(circ); + tab.post_select(Qubit(0), ChoiMixTableau::TableauSegment::Output); + std::pair res_uni = + cm_tableau_to_unitary_extension_circuit(tab, {}, {Qubit(0)}); + Eigen::MatrixXcd res_u = tket_sim::get_unitary(res_uni.first); + // q[0] was removed from the tableau by postselection so need to infer + // position in res_uni.second from the other qubits + SpPauliString zzz({Pauli::Z, Pauli::Z, Pauli::Z}); + zzz.set(res_uni.second.at(Qubit(1)), Pauli::I); + zzz.set(res_uni.second.at(Qubit(2)), Pauli::I); + Eigen::MatrixXcd z0 = zzz.to_sparse_matrix(3); + Eigen::MatrixXcd res_proj = 0.5 * (res_u + (z0 * res_u)); + for (unsigned r = 0; r < tab.get_n_rows(); ++r) { + ChoiMixTableau::row_tensor_t rrow = tab.get_row(r); + CmplxSpMat inmat = rrow.first.to_sparse_matrix(3); + Eigen::MatrixXcd inmatd = inmat; + QubitPauliMap outstr; + for (const std::pair& qp : rrow.second.string) + outstr.insert({res_uni.second.at(qp.first), qp.second}); + CmplxSpMat outmat = SpPauliString(outstr).to_sparse_matrix(3); + Eigen::MatrixXcd outmatd = outmat; + if (rrow.second.is_real_negative()) outmatd *= -1.; + CHECK((outmatd * res_proj * inmatd).isApprox(res_proj)); + } + } + GIVEN("Synthesising a tableau with all post-selections") { + Circuit circ = get_test_circ(); + ChoiMixTableau tab = circuit_to_cm_tableau(circ); + tab.post_select(Qubit(0), ChoiMixTableau::TableauSegment::Output); + tab.post_select(Qubit(1), ChoiMixTableau::TableauSegment::Output); + tab.post_select(Qubit(2), ChoiMixTableau::TableauSegment::Output); + Circuit res = cm_tableau_to_unitary_extension_circuit( + tab, {}, {Qubit(0), Qubit(1), Qubit(2)}) + .first.dagger(); + Eigen::VectorXcd res_sv = tket_sim::get_statevector(res); + for (unsigned r = 0; r < tab.get_n_rows(); ++r) { + ChoiMixTableau::row_tensor_t rrow = tab.get_row(r); + Eigen::MatrixXcd inmat = rrow.first.to_sparse_matrix(3); + if (rrow.second.is_real_negative()) inmat *= -1.; + CHECK((inmat * res_sv).isApprox(res_sv)); + } + } + GIVEN("Initialisations, collapses, discards and post-selections") { + Circuit circ(5); + circ.qubit_create(Qubit(1)); + circ.qubit_create(Qubit(2)); + circ.add_op(OpType::H, {4}); + circ.add_op(OpType::Collapse, {4}); + circ.add_op(OpType::CX, {4, 1}); + circ.add_op(OpType::CX, {4, 2}); + circ.add_op(OpType::CX, {4, 3}); + circ.add_op(OpType::H, {4}); + circ.add_op(OpType::H, {1}); + circ.add_op(OpType::V, {2}); + circ.add_op(OpType::CX, {1, 2}); + circ.add_op(OpType::CX, {1, 0}); + circ.qubit_discard(Qubit(0)); + ChoiMixTableau tab = circuit_to_cm_tableau(circ); + tab.post_select(Qubit(3), ChoiMixTableau::TableauSegment::Output); tab.canonical_column_order(); tab.gaussian_form(); + std::pair res_uni = + cm_tableau_to_unitary_extension_circuit(tab, {Qubit(1)}, {Qubit(0)}); + // First rebuild tableau by initialising, post-selecting, etc. + ChoiMixTableau res_tab = circuit_to_cm_tableau(res_uni.first); + qubit_map_t perm; + for (const std::pair& p : res_uni.second) + perm.insert({p.second, p.first}); + res_tab.rename_qubits(perm, ChoiMixTableau::TableauSegment::Output); + // Post-select/initialise + res_tab.post_select(Qubit(1), ChoiMixTableau::TableauSegment::Input); + res_tab.post_select(Qubit(0), ChoiMixTableau::TableauSegment::Output); + // Collapsing q[4] in X basis as per circ + res_tab.apply_gate( + OpType::H, {Qubit(4)}, ChoiMixTableau::TableauSegment::Output); + res_tab.collapse_qubit(Qubit(4), ChoiMixTableau::TableauSegment::Output); + res_tab.apply_gate( + OpType::H, {Qubit(4)}, ChoiMixTableau::TableauSegment::Output); + // Discarding q[0] also removes Z row for q[0], so recreate this by + // XCollapse at input + res_tab.apply_gate( + OpType::H, {Qubit(0)}, ChoiMixTableau::TableauSegment::Input); + res_tab.collapse_qubit(Qubit(0), ChoiMixTableau::TableauSegment::Input); + res_tab.apply_gate( + OpType::H, {Qubit(0)}, ChoiMixTableau::TableauSegment::Input); res_tab.canonical_column_order(); res_tab.gaussian_form(); REQUIRE(res_tab == tab); + + Eigen::MatrixXcd res_u = tket_sim::get_unitary(res_uni.first); + qubit_vector_t res_qbs = res_uni.first.all_qubits(); + // q[1] has no input terms, so initialise it + SpPauliString z1({Qubit(1), Pauli::Z}); + Eigen::MatrixXcd z1u = z1.to_sparse_matrix(res_qbs); + res_u = 0.5 * (res_u + (res_u * z1u)); + // q[0] has no output terms, so postselect it + SpPauliString z0({res_uni.second.at(Qubit(0)), Pauli::Z}); + Eigen::MatrixXcd z0u = z0.to_sparse_matrix(res_qbs); + res_u = 0.5 * (res_u + (z0u * res_u)); + + for (unsigned r = 0; r < tab.get_n_rows(); ++r) { + ChoiMixTableau::row_tensor_t rrow = tab.get_row(r); + Eigen::MatrixXcd inmat = rrow.first.to_sparse_matrix(res_qbs); + QubitPauliMap outstr; + for (const std::pair& qp : rrow.second.string) + outstr.insert({res_uni.second.at(qp.first), qp.second}); + Eigen::MatrixXcd outmat = SpPauliString(outstr).to_sparse_matrix(res_qbs); + if (rrow.second.is_real_negative()) outmat *= -1.; + CHECK((outmat * res_u * inmat).isApprox(res_u)); + } + } + GIVEN( + "A custom tableau with overlapping initialised and post-selected " + "qubits") { + std::list rows{ + {SpPauliStabiliser({Pauli::Z, Pauli::X, Pauli::I}), {}}, + {SpPauliStabiliser({Pauli::X, Pauli::Y, Pauli::Z}), {}}, + {{}, SpPauliStabiliser({Pauli::X, Pauli::X, Pauli::I})}, + {{}, SpPauliStabiliser({Pauli::I, Pauli::X, Pauli::X})}, + {SpPauliStabiliser({Pauli::I, Pauli::I, Pauli::Z}), + SpPauliStabiliser({Pauli::Z, Pauli::Z, Pauli::Z})}, + {SpPauliStabiliser({Pauli::Z, Pauli::I, Pauli::X}), + SpPauliStabiliser({Pauli::I, Pauli::I, Pauli::X})}, + }; + ChoiMixTableau tab(rows); + // Check the row constructor gets the right phases after internally + // transposing Ys + CHECK(tab.get_row(1).second.coeff == 0); + CHECK(tab.get_row(2).second.coeff == 0); + REQUIRE_THROWS(cm_tableau_to_unitary_extension_circuit(tab)); + std::pair res_uni = + cm_tableau_to_unitary_extension_circuit( + tab, {Qubit(3), Qubit(4)}, {Qubit(3), Qubit(4)}); + + ChoiMixTableau res_tab = circuit_to_cm_tableau(res_uni.first); + qubit_map_t perm; + for (const std::pair& p : res_uni.second) + perm.insert({p.second, p.first}); + res_tab.rename_qubits(perm, ChoiMixTableau::TableauSegment::Output); + res_tab.post_select(Qubit(3), ChoiMixTableau::TableauSegment::Input); + res_tab.post_select(Qubit(4), ChoiMixTableau::TableauSegment::Input); + res_tab.post_select(Qubit(3), ChoiMixTableau::TableauSegment::Output); + res_tab.post_select(Qubit(4), ChoiMixTableau::TableauSegment::Output); + res_tab.canonical_column_order(); + res_tab.gaussian_form(); + tab.canonical_column_order(); + tab.gaussian_form(); + REQUIRE(res_tab == tab); + + Eigen::MatrixXcd res_u = tket_sim::get_unitary(res_uni.first); + qubit_vector_t res_qbs = res_uni.first.all_qubits(); + // initialise q[3] and q[4] + SpPauliString z3i{Qubit(3), Pauli::Z}; + Eigen::MatrixXcd z3iu = z3i.to_sparse_matrix(res_qbs); + res_u = 0.5 * (res_u + (res_u * z3iu)); + SpPauliString z4i{Qubit(4), Pauli::Z}; + Eigen::MatrixXcd z4iu = z4i.to_sparse_matrix(res_qbs); + res_u = 0.5 * (res_u + (res_u * z4iu)); + // post-select q[3] and q[4] + SpPauliString z3o{res_uni.second.at(Qubit(3)), Pauli::Z}; + Eigen::MatrixXcd z3ou = z3o.to_sparse_matrix(res_qbs); + res_u = 0.5 * (res_u + (z3ou * res_u)); + SpPauliString z4o{res_uni.second.at(Qubit(4)), Pauli::Z}; + Eigen::MatrixXcd z4ou = z4o.to_sparse_matrix(res_qbs); + res_u = 0.5 * (res_u + (z4ou * res_u)); + + for (unsigned r = 0; r < tab.get_n_rows(); ++r) { + ChoiMixTableau::row_tensor_t rrow = tab.get_row(r); + Eigen::MatrixXcd inmat = rrow.first.to_sparse_matrix(res_qbs); + QubitPauliMap outstr; + for (const std::pair& qp : rrow.second.string) + outstr.insert({res_uni.second.at(qp.first), qp.second}); + Eigen::MatrixXcd outmat = SpPauliString(outstr).to_sparse_matrix(res_qbs); + if (rrow.second.is_real_negative()) outmat *= -1.; + CHECK((outmat * res_u * inmat).isApprox(res_u)); + } } } diff --git a/tket/test/src/test_Clifford.cpp b/tket/test/src/test_Clifford.cpp index 90d00212a4..fc61511e69 100644 --- a/tket/test/src/test_Clifford.cpp +++ b/tket/test/src/test_Clifford.cpp @@ -770,5 +770,194 @@ SCENARIO("Testing full clifford_simp") { } } +SCENARIO("Test push_cliffords_through_measures") { + GIVEN("Single qubit Cliffords") { + Circuit circ(3, 3); + circ.add_op(OpType::H, {0}); + circ.add_op(OpType::S, {1}); + circ.add_op(OpType::Y, {2}); + circ.add_measure(0, 0); + circ.add_measure(1, 1); + circ.add_measure(2, 2); + REQUIRE(!Transforms::push_cliffords_through_measures().apply(circ)); + } + GIVEN("Multi Cliffords") { + Circuit circ(3, 3); + circ.add_op(OpType::H, {0}); + circ.add_op(OpType::S, {1}); + circ.add_op(OpType::CX, {0, 1}); + circ.add_op(OpType::S, {1}); + circ.add_op(OpType::CX, {2, 1}); + circ.add_op(OpType::CX, {0, 1}); + circ.add_op(OpType::Y, {2}); + circ.add_op(OpType::H, {1}); + circ.add_op(OpType::S, {2}); + circ.add_op(OpType::CX, {2, 1}); + circ.add_op(OpType::S, {2}); + circ.add_op(OpType::CX, {0, 1}); + circ.add_op(OpType::CX, {0, 2}); + circ.add_op(OpType::X, {1}); + circ.add_measure(0, 0); + circ.add_measure(1, 1); + circ.add_measure(2, 2); + REQUIRE(Transforms::push_cliffords_through_measures().apply(circ)); + auto coms = circ.get_commands(); + REQUIRE(coms.size() == 14); + REQUIRE(coms[0].to_str() == "Measure q[2] --> c[2];"); + REQUIRE(coms[1].to_str() == "SetBits(1) permutation_scratch[3];"); + REQUIRE(coms[2].to_str() == "H q[0];"); + REQUIRE(coms[3].to_str() == "H q[1];"); + REQUIRE(coms[4].to_str() == "Measure q[0] --> c[0];"); + REQUIRE(coms[5].to_str() == "Measure q[1] --> c[1];"); + REQUIRE(coms[6].to_str() == "XOR c[0], permutation_scratch[0];"); + REQUIRE(coms[7].to_str() == "XOR c[1], permutation_scratch[1];"); + REQUIRE(coms[8].to_str() == "XOR c[0], permutation_scratch[2];"); + REQUIRE(coms[9].to_str() == "XOR c[2], permutation_scratch[1];"); + REQUIRE(coms[10].to_str() == "XOR c[2], permutation_scratch[2];"); + REQUIRE( + coms[11].to_str() == + "XOR permutation_scratch[3], permutation_scratch[1];"); + REQUIRE( + coms[12].to_str() == + "XOR permutation_scratch[3], permutation_scratch[2];"); + REQUIRE( + coms[13].to_str() == + "CopyBits permutation_scratch[0], permutation_scratch[1], " + "permutation_scratch[2], c[0], c[1], c[2];"); + } + GIVEN("Classical Circuit") { + Circuit circ(3, 3); + circ.add_op(OpType::X, {0}); + circ.add_op(OpType::CX, {0, 1}); + circ.add_op(OpType::CX, {1, 2}); + circ.add_op(OpType::X, {2}); + circ.add_op(OpType::CX, {2, 1}); + circ.add_op(OpType::CX, {1, 0}); + circ.add_measure(0, 0); + circ.add_measure(1, 1); + circ.add_measure(2, 2); + REQUIRE(Transforms::push_cliffords_through_measures().apply(circ)); + auto coms = circ.get_commands(); + REQUIRE(coms.size() == 12); + REQUIRE(coms[0].to_str() == "Measure q[0] --> c[0];"); + REQUIRE(coms[1].to_str() == "Measure q[1] --> c[1];"); + REQUIRE(coms[2].to_str() == "Measure q[2] --> c[2];"); + REQUIRE(coms[3].to_str() == "SetBits(1) permutation_scratch[3];"); + REQUIRE(coms[4].to_str() == "XOR c[0], permutation_scratch[0];"); + REQUIRE(coms[5].to_str() == "XOR c[2], permutation_scratch[1];"); + REQUIRE(coms[6].to_str() == "XOR c[0], permutation_scratch[2];"); + REQUIRE(coms[7].to_str() == "XOR c[2], permutation_scratch[0];"); + REQUIRE( + coms[8].to_str() == + "XOR permutation_scratch[3], permutation_scratch[1];"); + REQUIRE(coms[9].to_str() == "XOR c[1], permutation_scratch[2];"); + REQUIRE(coms[10].to_str() == "XOR c[2], permutation_scratch[2];"); + REQUIRE( + coms[11].to_str() == + "CopyBits permutation_scratch[0], permutation_scratch[1], " + "permutation_scratch[2], c[0], c[1], c[2];"); + } + GIVEN("Identity Circuit") { + Circuit circ(3, 3); + circ.add_op(OpType::H, {0}); + circ.add_op(OpType::S, {1}); + circ.add_op(OpType::V, {2}); + circ.add_op(OpType::CX, {0, 1}); + circ.add_op(OpType::CX, {1, 0}); + circ.add_op(OpType::CX, {0, 1}); + circ.add_op(OpType::CX, {1, 2}); + circ.add_op(OpType::CX, {2, 1}); + circ.add_op(OpType::CX, {1, 2}); + circ.add_op(OpType::H, {2}); + circ.add_op(OpType::Sdg, {0}); + circ.add_op(OpType::Vdg, {1}); + circ.add_measure(0, 0); + circ.add_measure(1, 1); + circ.add_measure(2, 2); + REQUIRE(Transforms::push_cliffords_through_measures().apply(circ)); + auto coms = circ.get_commands(); + REQUIRE(coms.size() == 8); + REQUIRE(coms[0].to_str() == "Measure q[0] --> c[0];"); + REQUIRE(coms[1].to_str() == "Measure q[1] --> c[1];"); + REQUIRE(coms[2].to_str() == "Measure q[2] --> c[2];"); + REQUIRE(coms[3].to_str() == "SetBits(1) permutation_scratch[3];"); + REQUIRE(coms[4].to_str() == "XOR c[1], permutation_scratch[0];"); + REQUIRE(coms[5].to_str() == "XOR c[2], permutation_scratch[1];"); + REQUIRE(coms[6].to_str() == "XOR c[0], permutation_scratch[2];"); + REQUIRE( + coms[7].to_str() == + "CopyBits permutation_scratch[0], permutation_scratch[1], " + "permutation_scratch[2], c[0], c[1], c[2];"); + } + GIVEN("Mixed Clifford and Non-Clifford circuit") { + Circuit circ(5, 5); + circ.add_op(OpType::H, {4}); + circ.add_op(OpType::S, {3}); + circ.add_op(OpType::CX, {3, 2}); + circ.add_op(OpType::S, {1}); + circ.add_op(OpType::T, {2}); + circ.add_op(OpType::T, {3}); + circ.add_op(OpType::CX, {4, 1}); + circ.add_op(OpType::CX, {2, 1}); + circ.add_op(OpType::H, {0}); + circ.add_op(OpType::S, {1}); + circ.add_op(OpType::CX, {0, 1}); + circ.add_op(OpType::S, {1}); + circ.add_op(OpType::CX, {3, 1}); + circ.add_op(OpType::CX, {0, 1}); + circ.add_op(OpType::Y, {2}); + circ.add_op(OpType::H, {1}); + circ.add_op(OpType::S, {2}); + circ.add_op(OpType::CX, {1, 4}); + circ.add_op(OpType::S, {2}); + circ.add_op(OpType::CX, {3, 1}); + circ.add_op(OpType::CX, {0, 2}); + circ.add_op(OpType::X, {1}); + circ.add_measure(0, 0); + circ.add_measure(1, 1); + circ.add_measure(2, 2); + circ.add_measure(3, 3); + circ.add_measure(4, 4); + REQUIRE(Transforms::push_cliffords_through_measures().apply(circ)); + auto coms = circ.get_commands(); + + REQUIRE(coms.size() == 26); + REQUIRE(coms[0].to_str() == "SetBits(1) permutation_scratch[5];"); + REQUIRE(coms[1].to_str() == "H q[0];"); + REQUIRE(coms[2].to_str() == "V q[1];"); + REQUIRE(coms[3].to_str() == "S q[3];"); + REQUIRE(coms[4].to_str() == "H q[4];"); + REQUIRE(coms[5].to_str() == "Measure q[0] --> c[0];"); + REQUIRE(coms[6].to_str() == "Measure q[1] --> c[1];"); + REQUIRE(coms[7].to_str() == "Measure q[4] --> c[4];"); + REQUIRE(coms[8].to_str() == "CX q[3], q[2];"); + REQUIRE(coms[9].to_str() == "XOR c[0], permutation_scratch[0];"); + REQUIRE(coms[10].to_str() == "XOR c[0], permutation_scratch[1];"); + REQUIRE(coms[11].to_str() == "XOR c[0], permutation_scratch[2];"); + REQUIRE(coms[12].to_str() == "XOR c[0], permutation_scratch[4];"); + REQUIRE(coms[13].to_str() == "T q[2];"); + REQUIRE(coms[14].to_str() == "T q[3];"); + REQUIRE(coms[15].to_str() == "Measure q[2] --> c[2];"); + REQUIRE(coms[16].to_str() == "Measure q[3] --> c[3];"); + REQUIRE(coms[17].to_str() == "XOR c[1], permutation_scratch[1];"); + REQUIRE(coms[18].to_str() == "XOR c[1], permutation_scratch[4];"); + REQUIRE(coms[19].to_str() == "XOR c[3], permutation_scratch[1];"); + REQUIRE(coms[20].to_str() == "XOR c[2], permutation_scratch[2];"); + REQUIRE(coms[21].to_str() == "XOR c[3], permutation_scratch[3];"); + REQUIRE(coms[22].to_str() == "XOR c[4], permutation_scratch[4];"); + REQUIRE( + coms[23].to_str() == + "XOR permutation_scratch[5], permutation_scratch[1];"); + REQUIRE( + coms[24].to_str() == + "XOR permutation_scratch[5], permutation_scratch[2];"); + REQUIRE( + coms[25].to_str() == + "CopyBits permutation_scratch[0], permutation_scratch[1], " + "permutation_scratch[2], permutation_scratch[3], " + "permutation_scratch[4], c[0], c[1], c[2], c[3], c[4];"); + } +} + } // namespace test_Clifford } // namespace tket diff --git a/tket/test/src/test_CompilerPass.cpp b/tket/test/src/test_CompilerPass.cpp index 43ed4d4b46..cb07c2e5fb 100644 --- a/tket/test/src/test_CompilerPass.cpp +++ b/tket/test/src/test_CompilerPass.cpp @@ -25,6 +25,7 @@ #include "tket/Circuit/Command.hpp" #include "tket/Circuit/PauliExpBoxes.hpp" #include "tket/Circuit/Simulation/CircuitSimulator.hpp" +#include "tket/Gate/SymTable.hpp" #include "tket/Mapping/LexiLabelling.hpp" #include "tket/OpType/OpType.hpp" #include "tket/OpType/OpTypeFunctions.hpp" @@ -269,8 +270,8 @@ SCENARIO("Test making (mostly routing) passes using PassGenerators") { {std::make_shared(), std::make_shared()}); - PassPtr all_passes = SynthesiseHQS() >> SynthesiseOQC() >> - SynthesiseUMD() >> SynthesiseTK() >> cp_route; + PassPtr all_passes = + SynthesiseOQC() >> SynthesiseUMD() >> SynthesiseTK() >> cp_route; REQUIRE(all_passes->apply(cu)); REQUIRE(cu.check_all_predicates()); } @@ -445,9 +446,9 @@ SCENARIO("Construct sequence pass") { } } -SCENARIO("Construct invalid sequence passes from vector") { +SCENARIO("Construct sequence pass that is invalid in strict mode") { std::vector invalid_pass_to_combo{ - SynthesiseHQS(), SynthesiseOQC(), SynthesiseUMD(), SynthesiseTK()}; + SynthesiseOQC(), SynthesiseUMD(), SynthesiseTK()}; for (const PassPtr& pass : invalid_pass_to_combo) { std::vector passes = {pass}; OpTypeSet ots = {OpType::CX}; @@ -458,6 +459,12 @@ SCENARIO("Construct invalid sequence passes from vector") { ppm, Transforms::id, pc, nlohmann::json{}); passes.push_back(compass); REQUIRE_THROWS_AS((void)SequencePass(passes), IncompatibleCompilerPasses); + GIVEN("A circuit compilable with non-strict SequencePass") { + PassPtr sequence = std::make_shared(passes, false); + Circuit circ(2); + CompilationUnit cu(circ); + REQUIRE_NOTHROW(sequence->apply(cu)); + } } } @@ -617,7 +624,8 @@ SCENARIO("gen_placement_pass test") { GIVEN("A simple circuit and device and base Placement.") { Circuit circ(4); add_2qb_gates(circ, OpType::CX, {{0, 1}, {2, 1}, {2, 3}}); - Architecture arc({{0, 1}, {1, 2}, {3, 2}}); + Architecture arc( + {{Node(0), Node(1)}, {Node(1), Node(2)}, {Node(3), Node(2)}}); Placement::Ptr plptr = std::make_shared(arc); PassPtr pp_place = gen_placement_pass(plptr); CompilationUnit cu(circ); @@ -632,7 +640,8 @@ SCENARIO("gen_placement_pass test") { GIVEN("A simple circuit and device and GraphPlacement.") { Circuit circ(4); add_2qb_gates(circ, OpType::CX, {{0, 1}, {2, 1}, {2, 3}}); - Architecture arc({{0, 1}, {1, 2}, {3, 2}}); + Architecture arc( + {{Node(0), Node(1)}, {Node(1), Node(2)}, {Node(3), Node(2)}}); Placement::Ptr plptr = std::make_shared(arc); PassPtr pp_place = gen_placement_pass(plptr); CompilationUnit cu(circ); @@ -1371,7 +1380,8 @@ SCENARIO("Commute measurements to the end of a circuit") { add_1qb_gates(test, OpType::X, {2, 2}); test.add_op(OpType::CX, {0, 2}); - Architecture line({{0, 1}, {1, 2}, {2, 3}}); + Architecture line( + {{Node(0), Node(1)}, {Node(1), Node(2)}, {Node(2), Node(3)}}); Placement::Ptr pp = std::make_shared(line); PassPtr route_pass = gen_full_mapping_pass( line, pp, @@ -1385,6 +1395,17 @@ SCENARIO("Commute measurements to the end of a circuit") { REQUIRE(type == OpType::Measure); // REQUIRE(final_command.get_args().front() == Node(3)); } + GIVEN("Measures targeting the same bit") { + // https://github.com/CQCL/tket/issues/1305 + Circuit c(4, 1); + c.add_op(OpType::CX, {0, 1}); + c.add_measure(3, 0); + c.add_measure(1, 0); + c.add_op(OpType::X, {1}); + c.add_op(OpType::CCX, {1, 3, 2}); + CompilationUnit cu(c); + CHECK_FALSE(try_delay_pass->apply(cu)); + } } SCENARIO("RemoveRedundancies and phase") { @@ -1416,6 +1437,15 @@ SCENARIO("RemoveRedundancies and phase") { REQUIRE(c1.get_commands().size() == 0); REQUIRE(equiv_val(c1.get_phase(), 1.)); } + GIVEN("A circuit with a Phase gate") { + Circuit c(2); + c.add_op(OpType::H, {0}); + c.add_op(OpType::Phase, 0.25, {}); + c.add_op(OpType::CX, {0, 1}); + CompilationUnit cu(c); + REQUIRE(RemoveRedundancies()->apply(cu)); + REQUIRE(cu.get_circ_ref().n_gates() == 2); + } } // Check whether a circuit maps all basis states to basis states. @@ -1432,7 +1462,11 @@ static bool is_classical_map(const Circuit& c) { SCENARIO("CX mapping pass") { // TKET-1045 GIVEN("A device with a linear architecture") { - Architecture line({{0, 1}, {1, 2}, {2, 3}, {3, 4}}); + Architecture line( + {{Node(0), Node(1)}, + {Node(1), Node(2)}, + {Node(2), Node(3)}, + {Node(3), Node(4)}}); // Noise-aware placement and rebase Placement::Ptr placer = std::make_shared(line); @@ -2039,5 +2073,71 @@ SCENARIO("PauliExponentials") { REQUIRE(test_unitary_comparison(c, cu.get_circ_ref(), true)); } } + +SCENARIO("GPI, GPI2 and AAMS operations") { + GIVEN("A circuit") { + Circuit c(3); + c.add_op(OpType::GPI, 0.1, {0}); + c.add_op(OpType::GPI2, 0.2, {1}); + c.add_op(OpType::AAMS, {0.3, 0.4, 0.5}, {0, 1}); + c.add_op(OpType::AAMS, {0.6, 0.7, 0.8}, {1, 2}); + c.add_op(OpType::GPI, 0.1, {1}); + c.add_op(OpType::GPI2, 0.2, {2}); + Circuit c_d = c.dagger(); + Circuit c2 = c; + c2.append(c_d); + Eigen::MatrixXcd u = tket_sim::get_unitary(c2); + REQUIRE(u.isApprox(Eigen::MatrixXcd::Identity(8, 8))); + Circuit c_t = c.transpose(); + CHECK(c_t.n_gates() == c.n_gates()); + CompilationUnit cu(c); + CHECK(FullPeepholeOptimise(true, OpType::CX)->apply(cu)); + REQUIRE(test_unitary_comparison(c, cu.get_circ_ref())); + CompilationUnit cu1(c); + CHECK(gen_rebase_pass_via_tk2( + {OpType::PhasedX, OpType::Rz, OpType::CX}, + CircPool::TK2_using_CX_and_swap, CircPool::tk1_to_PhasedXRz) + ->apply(cu1)); + REQUIRE(test_unitary_comparison(c, cu1.get_circ_ref())); + } + GIVEN("Rebasing a symbolic AAAS gate") { + Circuit circ(2); + auto a = SymTable::fresh_symbol("a"); + auto b = SymTable::fresh_symbol("b"); + auto c = SymTable::fresh_symbol("c"); + auto ea = Expr(a); + auto eb = Expr(b); + auto ec = Expr(c); + SymEngine::map_basic_basic sub_map{ + std::make_pair(a, Expr(0.1)), std::make_pair(b, Expr(0.2)), + std::make_pair(c, Expr(0.3))}; + circ.add_op(OpType::AAMS, {Expr(a), Expr(b), Expr(c)}, {0, 1}); + CompilationUnit cu(circ); + CHECK(gen_rebase_pass_via_tk2( + {OpType::PhasedX, OpType::Rz, OpType::CX}, + CircPool::TK2_using_CX_and_swap, CircPool::tk1_to_PhasedXRz) + ->apply(cu)); + Circuit circ1 = cu.get_circ_ref(); + circ1.symbol_substitution(sub_map); + Circuit circ2(2); + circ2.add_op(OpType::AAMS, {0.1, 0.2, 0.3}, {0, 1}); + REQUIRE(test_unitary_comparison(circ1, circ2)); + } + GIVEN("A circuit made of Clifford gates") { + Circuit c(3); + c.add_op(OpType::GPI, 0.25, {0}); + c.add_op(OpType::GPI2, 0.5, {1}); + c.add_op(OpType::AAMS, {0, 0.1, 0.2}, {0, 1}); + c.add_op(OpType::AAMS, {1., 0.25, 0.75}, {1, 2}); + c.add_op(OpType::AAMS, {0.5, 0.5, 1.5}, {0, 1}); + REQUIRE(CliffordCircuitPredicate().verify(c)); + } + GIVEN("A circuit with a non-Clifford AAMS gate") { + Circuit c(2); + c.add_op(OpType::AAMS, {1., 0, 0.1}, {0, 1}); + REQUIRE_FALSE(CliffordCircuitPredicate().verify(c)); + } +} + } // namespace test_CompilerPass } // namespace tket diff --git a/tket/test/src/test_Diagonalisation.cpp b/tket/test/src/test_Diagonalisation.cpp new file mode 100644 index 0000000000..a2778dc07a --- /dev/null +++ b/tket/test/src/test_Diagonalisation.cpp @@ -0,0 +1,148 @@ +// 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/Simulation/CircuitSimulator.hpp" +#include "tket/Diagonalisation/Diagonalisation.hpp" + +namespace tket { + +namespace test_Diagonalisation { + +SCENARIO("Matrix tests for reducing a Pauli to Z") { + std::list test_paulis{Pauli::I, Pauli::X, Pauli::Y, Pauli::Z}; + std::list test_configs = { + CXConfigType::Snake, CXConfigType::Tree, CXConfigType::Star, + CXConfigType::MultiQGate}; + for (const Pauli& p : test_paulis) { + // If p is Pauli::I, it is dropped from the sparse representation in the + // constructor, so need to add Qubit(3) to the circuit and sparse matrix + // explicitly + SpPauliStabiliser pt({Pauli::X, Pauli::Y, Pauli::Z, p}); + CmplxSpMat pt_u = pt.to_sparse_matrix(4); + Eigen::MatrixXcd pt_ud = pt_u; + for (const CXConfigType& config : test_configs) { + std::pair diag = reduce_pauli_to_z(pt, config); + if (p == Pauli::I) diag.first.add_qubit(Qubit(3)); + Eigen::MatrixXcd diag_u = tket_sim::get_unitary(diag.first); + CmplxSpMat z_u = SpPauliString(diag.second, Pauli::Z).to_sparse_matrix(4); + Eigen::MatrixXcd z_ud = z_u; + CHECK((z_ud * diag_u * pt_ud).isApprox(diag_u)); + } + } +} + +SCENARIO("Matrix tests for reducing two anticommuting Paulis to Z X") { + std::list non_trivials{Pauli::X, Pauli::Y, Pauli::Z}; + std::list test_configs = { + CXConfigType::Snake, CXConfigType::Tree, CXConfigType::Star, + CXConfigType::MultiQGate}; + // Loop through all commuting options for two qubits + for (const Pauli& p0 : non_trivials) { + for (const Pauli& p1 : non_trivials) { + SpPauliStabiliser p({Pauli::Z, p0, p1, Pauli::Z}); + CmplxSpMat p_u = p.to_sparse_matrix(); + Eigen::MatrixXcd p_ud = p_u; + for (const Pauli& q0 : non_trivials) { + for (const Pauli& q1 : non_trivials) { + SpPauliStabiliser q({Pauli::X, q0, q1, Pauli::Z}); + if (p.commutes_with(q)) continue; + CmplxSpMat q_u = q.to_sparse_matrix(); + Eigen::MatrixXcd q_ud = q_u; + for (const CXConfigType& config : test_configs) { + std::pair diag = + reduce_anticommuting_paulis_to_z_x(p, q, config); + Eigen::MatrixXcd diag_u = tket_sim::get_unitary(diag.first); + CmplxSpMat z_u = + SpPauliString(diag.second, Pauli::Z).to_sparse_matrix(4); + Eigen::MatrixXcd z_ud = z_u; + CmplxSpMat x_u = + SpPauliString(diag.second, Pauli::X).to_sparse_matrix(4); + Eigen::MatrixXcd x_ud = x_u; + CHECK((z_ud * diag_u * p_ud).isApprox(diag_u)); + CHECK((x_ud * diag_u * q_ud).isApprox(diag_u)); + } + } + } + } + } +} + +SCENARIO("Matrix tests for reducing two commuting Paulis to Z X") { + std::list paulis{Pauli::I, Pauli::X, Pauli::Y, Pauli::Z}; + std::list test_configs = { + CXConfigType::Snake, CXConfigType::Tree, CXConfigType::Star, + CXConfigType::MultiQGate}; + // Loop through all commuting options for two qubits + for (const Pauli& p0 : paulis) { + for (const Pauli& p1 : paulis) { + SpPauliStabiliser p({Pauli::Z, p0, p1, Pauli::Z}); + CmplxSpMat p_u = p.to_sparse_matrix(4); + Eigen::MatrixXcd p_ud = p_u; + for (const Pauli& q0 : paulis) { + for (const Pauli& q1 : paulis) { + SpPauliStabiliser q({Pauli::Z, q0, q1, Pauli::I}); + if (!p.commutes_with(q)) continue; + CmplxSpMat q_u = q.to_sparse_matrix(4); + Eigen::MatrixXcd q_ud = q_u; + for (const CXConfigType& config : test_configs) { + std::tuple diag = + reduce_commuting_paulis_to_zi_iz(p, q, config); + Circuit& circ = std::get<0>(diag); + // In cases with matching Pauli::Is, the circuit produced may not + // include all qubits + for (unsigned i = 0; i < 4; ++i) circ.add_qubit(Qubit(i), false); + Eigen::MatrixXcd diag_u = tket_sim::get_unitary(circ); + CmplxSpMat zi_u = + SpPauliString(std::get<1>(diag), Pauli::Z).to_sparse_matrix(4); + Eigen::MatrixXcd zi_ud = zi_u; + CmplxSpMat iz_u = + SpPauliString(std::get<2>(diag), Pauli::Z).to_sparse_matrix(4); + Eigen::MatrixXcd iz_ud = iz_u; + CHECK((zi_ud * diag_u * p_ud).isApprox(diag_u)); + CHECK((iz_ud * diag_u * q_ud).isApprox(diag_u)); + } + } + } + } + } +} + +SCENARIO("Reducing shared qubits to no matches") { + GIVEN("Strings with no mismatch and second completely contains the first") { + SpPauliStabiliser pauli0({{Qubit(0), Pauli::X}, {Qubit(1), Pauli::Y}}); + SpPauliStabiliser pauli1( + {{Qubit(0), Pauli::X}, {Qubit(1), Pauli::Y}, {Qubit(2), Pauli::Z}}); + SpPauliStabiliser p0_orig = pauli0; + SpPauliStabiliser p1_orig = pauli1; + std::pair> sol = + reduce_overlap_of_paulis(pauli0, pauli1, CXConfigType::Snake, false); + // Check there is no final overlapping qubit returned + CHECK(!sol.second); + CHECK(pauli0.common_qubits(pauli1).empty()); + CHECK(pauli0.conflicting_qubits(pauli1).empty()); + // Check the strings are updated correctly to match the unitary + Eigen::MatrixXcd diag_u = tket_sim::get_unitary(sol.first); + Eigen::MatrixXcd p0_u = pauli0.to_sparse_matrix(3); + Eigen::MatrixXcd p0_o_u = p0_orig.to_sparse_matrix(3); + CHECK((p0_u * diag_u * p0_o_u).isApprox(diag_u)); + Eigen::MatrixXcd p1_u = pauli1.to_sparse_matrix(3); + Eigen::MatrixXcd p1_o_u = p1_orig.to_sparse_matrix(3); + CHECK((p1_u * diag_u * p1_o_u).isApprox(diag_u)); + } +} + +} // namespace test_Diagonalisation +} // namespace tket \ No newline at end of file diff --git a/tket/test/src/test_LexiRoute.cpp b/tket/test/src/test_LexiRoute.cpp index f7cc42ccdb..3f2c7d21e4 100644 --- a/tket/test/src/test_LexiRoute.cpp +++ b/tket/test/src/test_LexiRoute.cpp @@ -995,7 +995,11 @@ SCENARIO( test_circuit.add_blank_wires(4); add_2qb_gates(test_circuit, OpType::CX, {{0, 1}, {1, 2}, {2, 3}, {3, 0}}); - Architecture test_arc({{0, 1}, {1, 2}, {2, 3}, {3, 0}}); + Architecture test_arc( + {{Node(0), Node(1)}, + {Node(1), Node(2)}, + {Node(2), Node(3)}, + {Node(3), Node(0)}}); Placement test_p(test_arc); std::map map_; @@ -1020,7 +1024,8 @@ SCENARIO("Empty Circuit test") { GIVEN("An Empty Circuit") { Circuit circ; circ.add_blank_wires(4); - Architecture arc({{0, 1}, {1, 2}, {2, 3}}); + Architecture arc( + {{Node(0), Node(1)}, {Node(1), Node(2)}, {Node(2), Node(3)}}); MappingManager mm(std::make_shared(arc)); REQUIRE(mm.route_circuit( circ, { @@ -1040,7 +1045,8 @@ SCENARIO("Routing on circuit with no multi-qubit gates") { circ.add_op(OpType::Y, {1}); unsigned orig_vertices = circ.n_vertices(); - Architecture arc({{0, 1}, {1, 2}, {2, 3}}); + Architecture arc( + {{Node(0), Node(1)}, {Node(1), Node(2)}, {Node(2), Node(3)}}); MappingManager mm(std::make_shared(arc)); REQUIRE(mm.route_circuit( circ, { @@ -1056,8 +1062,8 @@ SCENARIO("Test routing on a directed architecture with bidirectional edges") { Circuit circ(2); circ.add_op(OpType::H, {0}); circ.add_op(OpType::CX, {0, 1}); - Architecture arc({{0, 1}, {1, 0}}); - Architecture arc2(std::vector>{{0, 1}}); + Architecture arc({{Node(0), Node(1)}, {Node(1), Node(0)}}); + Architecture arc2({std::pair{Node(0), Node(1)}}); // routing ignored bi directional edge and solves correctly MappingManager mm(std::make_shared(arc)); @@ -1085,7 +1091,7 @@ SCENARIO( circ.add_op(OpType::CRz, 0.5, {1, 0}); circ.add_op(OpType::CRz, 0.5, {0, 1}); - Architecture arc(std::vector>{{0, 1}}); + Architecture arc({std::pair{Node(0), Node(1)}}); MappingManager mm(std::make_shared(arc)); REQUIRE(mm.route_circuit( circ, {std::make_shared(), @@ -1108,13 +1114,13 @@ SCENARIO("Dense CX circuits route succesfully") { } } } - Architecture arc( - {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {0, 5}, {1, 6}, {1, 7}, - {2, 6}, {2, 7}, {3, 8}, {3, 9}, {4, 8}, {4, 9}, {5, 6}, - {5, 10}, {5, 11}, {6, 10}, {6, 11}, {6, 7}, {7, 12}, {7, 13}, - {7, 8}, {8, 12}, {8, 13}, {8, 9}, {10, 11}, {11, 16}, {11, 17}, - {11, 12}, {12, 16}, {12, 17}, {12, 13}, {13, 18}, {13, 19}, {13, 14}, - {14, 18}, {14, 19}, {15, 16}, {16, 17}, {17, 18}, {18, 19}}); + Architecture arc(std::vector>{ + {0, 1}, {1, 2}, {2, 3}, {3, 4}, {0, 5}, {1, 6}, {1, 7}, + {2, 6}, {2, 7}, {3, 8}, {3, 9}, {4, 8}, {4, 9}, {5, 6}, + {5, 10}, {5, 11}, {6, 10}, {6, 11}, {6, 7}, {7, 12}, {7, 13}, + {7, 8}, {8, 12}, {8, 13}, {8, 9}, {10, 11}, {11, 16}, {11, 17}, + {11, 12}, {12, 16}, {12, 17}, {12, 13}, {13, 18}, {13, 19}, {13, 14}, + {14, 18}, {14, 19}, {15, 16}, {16, 17}, {17, 18}, {18, 19}}); MappingManager mm(std::make_shared(arc)); REQUIRE(mm.route_circuit( circ, {std::make_shared(), @@ -1166,22 +1172,22 @@ SCENARIO( } } } - Architecture arc( - {{0, 1}, - {2, 0}, - {2, 4}, - {6, 4}, - {8, 6}, - {8, 10}, - {12, 10}, - {3, 1}, - {3, 5}, - {7, 5}, - {7, 9}, - {11, 9}, - {11, 13}, - {12, 13}, - {6, 7}}); + Architecture arc(std::vector>{ + {0, 1}, + {2, 0}, + {2, 4}, + {6, 4}, + {8, 6}, + {8, 10}, + {12, 10}, + {3, 1}, + {3, 5}, + {7, 5}, + {7, 9}, + {11, 9}, + {11, 13}, + {12, 13}, + {6, 7}}); MappingManager mm(std::make_shared(arc)); REQUIRE(mm.route_circuit( circ, {std::make_shared(), @@ -1525,7 +1531,11 @@ SCENARIO("Initial map should contain all data qubits") { } } SCENARIO("Unlabelled qubits should be assigned to ancilla qubits.") { - Architecture arc({{0, 1}, {1, 2}, {2, 3}, {3, 0}}); + Architecture arc( + {{Node(0), Node(1)}, + {Node(1), Node(2)}, + {Node(2), Node(3)}, + {Node(3), Node(0)}}); Circuit c(4); c.add_op(OpType::CZ, {0, 3}); c.add_op(OpType::CZ, {1, 0}); @@ -1556,7 +1566,7 @@ SCENARIO("Unlabelled qubits should be assigned to ancilla qubits.") { } SCENARIO("Lexi relabel with partially mapped circuit") { GIVEN("With an unplaced qubit") { - Architecture arc({{0, 1}, {1, 2}}); + Architecture arc({{Node(0), Node(1)}, {Node(1), Node(2)}}); Circuit c(3); c.add_op(OpType::CZ, {0, 1}, "cz0,1"); c.add_op(OpType::CZ, {1, 2}, "cz1,2"); @@ -1587,7 +1597,12 @@ SCENARIO("Lexi relabel with partially mapped circuit") { c.add_op(OpType::CZ, {1, 3}, "cz1,3"); c.add_op(OpType::CZ, {3, 2}, "cz3,2"); - Architecture arc({{0, 1}, {0, 2}, {0, 3}, {4, 1}, {4, 2}}); + Architecture arc( + {{Node(0), Node(1)}, + {Node(0), Node(2)}, + {Node(0), Node(3)}, + {Node(4), Node(1)}, + {Node(4), Node(2)}}); PassPtr plac_p = gen_placement_pass(std::make_shared(arc)); CompilationUnit cu(c); REQUIRE(plac_p->apply(cu)); @@ -1614,11 +1629,11 @@ SCENARIO("Test failing case") { std::ifstream circuit_file("lexiroute_circuit.json"); nlohmann::json j = nlohmann::json::parse(circuit_file); auto c = j.get(); - Architecture arc({{0, 1}, {1, 2}, {2, 3}, {3, 5}, {4, 1}, {4, 7}, - {5, 8}, {6, 7}, {7, 10}, {8, 9}, {8, 11}, {10, 12}, - {11, 14}, {12, 13}, {14, 13}, {14, 16}, {12, 15}, {15, 18}, - {17, 18}, {16, 19}, {19, 20}, {18, 21}, {21, 23}, {19, 22}, - {22, 25}, {23, 24}, {24, 25}, {25, 26}}); + Architecture arc(std::vector>{ + {0, 1}, {1, 2}, {2, 3}, {3, 5}, {4, 1}, {4, 7}, {5, 8}, + {6, 7}, {7, 10}, {8, 9}, {8, 11}, {10, 12}, {11, 14}, {12, 13}, + {14, 13}, {14, 16}, {12, 15}, {15, 18}, {17, 18}, {16, 19}, {19, 20}, + {18, 21}, {21, 23}, {19, 22}, {22, 25}, {23, 24}, {24, 25}, {25, 26}}); CompilationUnit cu(c); PassPtr r_p = gen_routing_pass( diff --git a/tket/test/src/test_MappingVerification.cpp b/tket/test/src/test_MappingVerification.cpp index ddf1a46269..4bc6893dd3 100644 --- a/tket/test/src/test_MappingVerification.cpp +++ b/tket/test/src/test_MappingVerification.cpp @@ -25,11 +25,15 @@ SCENARIO( "Test validity of circuit against architecture using " "respects_connectivity_constraints method.", "[routing]") { - Architecture arc({{1, 0}, {1, 2}}); + Architecture arc({{Node(1), Node(0)}, {Node(1), Node(2)}}); GIVEN("A simple CX circuit and a line_placement map.") { Circuit circ(5); add_2qb_gates(circ, OpType::CX, {{0, 1}, {0, 3}, {2, 4}, {1, 4}, {0, 4}}); - Architecture test_arc({{0, 1}, {1, 2}, {2, 3}, {3, 4}}); + Architecture test_arc( + {{Node(0), Node(1)}, + {Node(1), Node(2)}, + {Node(2), Node(3)}, + {Node(3), Node(4)}}); LinePlacement lp_obj(test_arc); lp_obj.place(circ); MappingManager mm(std::make_shared(test_arc)); diff --git a/tket/test/src/test_PauliGraph.cpp b/tket/test/src/test_PauliGraph.cpp index 2895899f09..7ddd257586 100644 --- a/tket/test/src/test_PauliGraph.cpp +++ b/tket/test/src/test_PauliGraph.cpp @@ -18,10 +18,10 @@ #include "CircuitsForTesting.hpp" #include "testutil.hpp" #include "tket/Circuit/Boxes.hpp" +#include "tket/Circuit/CircUtils.hpp" #include "tket/Circuit/PauliExpBoxes.hpp" #include "tket/Circuit/Simulation/CircuitSimulator.hpp" #include "tket/Converters/Converters.hpp" -#include "tket/Converters/PauliGadget.hpp" #include "tket/Diagonalisation/Diagonalisation.hpp" #include "tket/Gate/SymTable.hpp" #include "tket/PauliGraph/ConjugatePauliFunctions.hpp" @@ -920,13 +920,13 @@ SCENARIO("Diagonalise a pair of gadgets") { Circuit correct; for (unsigned i = 0; i < 2; ++i) { - append_single_pauli_gadget(correct, gadgets.at(i)); + correct.append(pauli_gadget(gadgets.at(i))); } auto u_correct = tket_sim::get_unitary(correct); GIVEN("Snake configuration") { CXConfigType config = CXConfigType::Snake; - append_pauli_gadget_pair(circ, gadgets.at(0), gadgets.at(1), config); + circ.append(pauli_gadget_pair(gadgets.at(0), gadgets.at(1), config)); THEN("Unitary is correct") { auto u_res = tket_sim::get_unitary(circ); REQUIRE((u_correct - u_res).cwiseAbs().sum() < ERR_EPS); @@ -934,7 +934,7 @@ SCENARIO("Diagonalise a pair of gadgets") { } GIVEN("Star configuration") { CXConfigType config = CXConfigType::Star; - append_pauli_gadget_pair(circ, gadgets.at(0), gadgets.at(1), config); + circ.append(pauli_gadget_pair(gadgets.at(0), gadgets.at(1), config)); THEN("Unitary is correct") { auto u_res = tket_sim::get_unitary(circ); REQUIRE((u_correct - u_res).cwiseAbs().sum() < ERR_EPS); @@ -942,7 +942,7 @@ SCENARIO("Diagonalise a pair of gadgets") { } GIVEN("Tree configuration") { CXConfigType config = CXConfigType::Tree; - append_pauli_gadget_pair(circ, gadgets.at(0), gadgets.at(1), config); + circ.append(pauli_gadget_pair(gadgets.at(0), gadgets.at(1), config)); THEN("Unitary is correct") { auto u_res = tket_sim::get_unitary(circ); REQUIRE((u_correct - u_res).cwiseAbs().sum() < ERR_EPS); @@ -950,7 +950,7 @@ SCENARIO("Diagonalise a pair of gadgets") { } GIVEN("MultiQGate configuration") { CXConfigType config = CXConfigType::MultiQGate; - append_pauli_gadget_pair(circ, gadgets.at(0), gadgets.at(1), config); + circ.append(pauli_gadget_pair(gadgets.at(0), gadgets.at(1), config)); circ.decompose_boxes_recursively(); THEN("XXPhase3 were used") { REQUIRE(circ.count_gates(OpType::XXPhase3) == 2); diff --git a/tket/test/src/test_PhaseGadget.cpp b/tket/test/src/test_PhaseGadget.cpp index 9be60aa248..56f5182843 100644 --- a/tket/test/src/test_PhaseGadget.cpp +++ b/tket/test/src/test_PhaseGadget.cpp @@ -149,7 +149,8 @@ SCENARIO("Constructing Pauli gadgets") { 1.*i_, 0., 0., 0.; // clang-format on Eigen::Matrix4cd m = (+0.5 * PI * i_ * t * a).exp(); - Circuit circ = pauli_gadget({Pauli::X, Pauli::Y}, t); + Circuit circ = + pauli_gadget(SpSymPauliTensor(DensePauliMap{Pauli::X, Pauli::Y}, t)); const Eigen::Matrix4cd u = tket_sim::get_unitary(circ); Eigen::Matrix4cd v = m * u; REQUIRE((v - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -286,7 +287,7 @@ SCENARIO("Decompose phase gadgets") { for (unsigned i = 0; i < n_qubits; ++i) { nZ.push_back(Pauli::Z); } - Circuit pauli_gadget_circ = pauli_gadget(nZ, 0.2); + Circuit pauli_gadget_circ = pauli_gadget(SpSymPauliTensor(nZ, 0.2)); pauli_gadget_circ.decompose_boxes_recursively(); Circuit phase_gadget_circ = phase_gadget(n_qubits, 0.2, config); phase_gadget_circ.decompose_boxes_recursively(); diff --git a/tket/test/src/test_PhasedXFrontier.cpp b/tket/test/src/test_PhasedXFrontier.cpp index 6ad8f172e2..786d0ef5d3 100644 --- a/tket/test/src/test_PhasedXFrontier.cpp +++ b/tket/test/src/test_PhasedXFrontier.cpp @@ -297,5 +297,59 @@ SCENARIO("PhasedXFrontier: Squashing PhasedX") { } } +SCENARIO("PhasedXFrontier: Compute intervals") { + GIVEN("A simple circuit") { + Circuit c(2, 1); + c.add_op(OpType::PhasedX, {0.4, 0.3}, {0}); + c.add_op(OpType::PhasedX, {0.4, 0.3}, {1}); + auto first_cx = c.add_op(OpType::CX, {0, 1}); + c.add_op(OpType::PhasedX, {0.4, 0.3}, {0}); + auto ctrl_phasedx = c.add_conditional_gate( + OpType::PhasedX, {0.4, 0.3}, {0}, {0}, 1); + c.add_op(OpType::PhasedX, {0.4, 0.3}, {1}); + auto ctrl_cx = + c.add_conditional_gate(OpType::CX, {}, {0, 1}, {0}, 1); + + Transforms::PhasedXFrontier frontier(c); + Transforms::PhasedXFrontierTester frontier_tester(frontier, c); + + THEN("Initial intervals: [input, first_cx] and [input, first_cx]") { + auto inputs = c.all_inputs(); + REQUIRE(frontier_tester.get_interval_start(0) == inputs[0]); + REQUIRE(frontier_tester.get_interval_start(1) == inputs[1]); + REQUIRE(frontier_tester.get_interval_end(0) == first_cx); + REQUIRE(frontier_tester.get_interval_end(1) == first_cx); + } + + // Move frontier forward + frontier.next_multiqb(first_cx); + THEN("Second intervals: [first_cx, ctrl_phasedx] and [first_cx, ctrl_cx]") { + REQUIRE(frontier_tester.get_interval_start(0) == first_cx); + REQUIRE(frontier_tester.get_interval_start(1) == first_cx); + REQUIRE(frontier_tester.get_interval_end(0) == ctrl_phasedx); + REQUIRE(frontier_tester.get_interval_end(1) == ctrl_cx); + } + + // Move frontier forward + frontier.next_multiqb(ctrl_phasedx); + THEN("Third intervals: [ctrl_phasedx, ctrl_cx] and [first_cx, ctrl_cx]") { + REQUIRE(frontier_tester.get_interval_start(0) == ctrl_phasedx); + REQUIRE(frontier_tester.get_interval_start(1) == first_cx); + REQUIRE(frontier_tester.get_interval_end(0) == ctrl_cx); + REQUIRE(frontier_tester.get_interval_end(1) == ctrl_cx); + } + + // Move frontier forward + frontier.next_multiqb(ctrl_cx); + THEN("Final intervals: [ctrl_cx, output] and [ctrl_cx, output]") { + auto outputs = c.all_outputs(); + REQUIRE(frontier_tester.get_interval_start(0) == ctrl_cx); + REQUIRE(frontier_tester.get_interval_start(1) == ctrl_cx); + REQUIRE(frontier_tester.get_interval_end(0) == outputs[0]); + REQUIRE(frontier_tester.get_interval_end(1) == outputs[1]); + } + } +} + } // namespace test_PhasedXFrontier } // namespace tket diff --git a/tket/test/src/test_Predicates.cpp b/tket/test/src/test_Predicates.cpp index ad9925b82f..0b547a7595 100644 --- a/tket/test/src/test_Predicates.cpp +++ b/tket/test/src/test_Predicates.cpp @@ -438,7 +438,13 @@ SCENARIO("Test PlacementPredicate") { GIVEN( "Does the Placement class correctly modify Circuits and return " "maps?") { - Architecture test_arc({{0, 1}, {1, 2}, {1, 3}, {1, 4}, {2, 3}, {2, 5}}); + Architecture test_arc( + {{Node(0), Node(1)}, + {Node(1), Node(2)}, + {Node(1), Node(3)}, + {Node(1), Node(4)}, + {Node(2), Node(3)}, + {Node(2), Node(5)}}); PredicatePtr placement_pred = std::make_shared(test_arc); @@ -476,7 +482,8 @@ SCENARIO("Test PlacementPredicate") { SCENARIO("Test ConnectivityPredicate") { // https://github.com/CQCL/tket/issues/683 - Architecture arc({{0, 1}, {0, 2}, {1, 2}}); + Architecture arc( + {{Node(0), Node(1)}, {Node(0), Node(2)}, {Node(1), Node(2)}}); Circuit c(2, 2); c.add_conditional_gate(OpType::Phase, {0.5}, {}, {0}, 1); PredicatePtr conn = std::make_shared(arc); diff --git a/tket/test/src/test_RoutingPasses.cpp b/tket/test/src/test_RoutingPasses.cpp index 4a126999d2..f25fa9d25f 100644 --- a/tket/test/src/test_RoutingPasses.cpp +++ b/tket/test/src/test_RoutingPasses.cpp @@ -41,7 +41,12 @@ namespace tket { using Connection = Architecture::Connection; SCENARIO("Test decompose_SWAP_to_CX pass", ) { - Architecture arc({{0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 0}}); + Architecture arc( + {{Node(0), Node(1)}, + {Node(1), Node(2)}, + {Node(2), Node(3)}, + {Node(3), Node(4)}, + {Node(4), Node(0)}}); GIVEN("A single SWAP gate. Finding if correct number of vertices added") { Circuit circ(5); circ.add_op(OpType::SWAP, {0, 1}); @@ -154,7 +159,7 @@ SCENARIO("Test decompose_SWAP_to_CX pass", ) { GIVEN( "A circuit that with no CX gates, but with directed architecture, " "opposite case.") { - Architecture dummy_arc({{1, 0}}); + Architecture dummy_arc({std::pair(Node(1), Node(0))}); Circuit circ(2); circ.add_op(OpType::SWAP, {1, 0}); reassign_boundary(circ); @@ -208,7 +213,7 @@ SCENARIO("Test decompose_SWAP_to_CX pass", ) { } SCENARIO("Test redirect_CX_gates pass", "[routing]") { - Architecture arc({{1, 0}, {1, 2}}); + Architecture arc({{Node(1), Node(0)}, {Node(1), Node(2)}}); GIVEN("A circuit that requires no redirection.") { Circuit circ(3); add_2qb_gates(circ, OpType::CX, {{1, 0}, {1, 2}}); @@ -356,7 +361,7 @@ SCENARIO( "Methods related to correct routing and decomposition of circuits with " "classical wires.") { GIVEN("A circuit with classical wires on CX gates.") { - Architecture test_arc({{0, 1}, {1, 2}}); + Architecture test_arc({{Node(0), Node(1)}, {Node(1), Node(2)}}); Circuit circ(3, 2); circ.add_op(OpType::CX, {0, 1}); circ.add_op(OpType::H, {0}); @@ -379,7 +384,11 @@ SCENARIO( GIVEN( "A circuit that requires modification to satisfy architecture " "constraints.") { - Architecture arc({{0, 1}, {1, 2}, {2, 3}, {3, 4}}); + Architecture arc( + {{Node(0), Node(1)}, + {Node(1), Node(2)}, + {Node(2), Node(3)}, + {Node(3), Node(4)}}); Circuit circ(5, 1); circ.add_conditional_gate(OpType::CX, {}, {0, 1}, {0}, 1); add_2qb_gates(circ, OpType::CX, {{0, 1}, {1, 2}, {1, 3}, {1, 4}, {0, 1}}); @@ -398,7 +407,7 @@ SCENARIO( REQUIRE(classical_com.get_args()[0] == circ.all_bits()[0]); } GIVEN("A single Bridge gate with multiple classical wires, decomposed.") { - Architecture arc({{0, 1}, {1, 2}}); + Architecture arc({{Node(0), Node(1)}, {Node(1), Node(2)}}); Circuit circ(3, 3); circ.add_conditional_gate( OpType::BRIDGE, {}, {0, 1, 2}, {0, 1, 2}, 1); @@ -413,7 +422,7 @@ SCENARIO( } } GIVEN("A directed architecture, a single CX gate that requires flipping.") { - Architecture arc(std::vector>{{0, 1}}); + Architecture arc({std::pair{Node(0), Node(1)}}); Circuit circ(2, 2); circ.add_conditional_gate(OpType::CX, {}, {0, 1}, {1, 0}, 0); circ.add_conditional_gate(OpType::CX, {}, {1, 0}, {0, 1}, 1); @@ -480,7 +489,13 @@ SCENARIO( {0, 4}, {2, 1}, {0, 3}}); - Architecture arc({{1, 0}, {0, 2}, {1, 2}, {2, 3}, {2, 4}, {4, 3}}); + Architecture arc( + {{Node(1), Node(0)}, + {Node(0), Node(2)}, + {Node(1), Node(2)}, + {Node(2), Node(3)}, + {Node(2), Node(4)}, + {Node(4), Node(3)}}); MappingManager mm(std::make_shared(arc)); REQUIRE(mm.route_circuit( circ, {std::make_shared(), diff --git a/tket/test/src/test_Synthesis.cpp b/tket/test/src/test_Synthesis.cpp index 6d652810e2..124d92c85a 100644 --- a/tket/test/src/test_Synthesis.cpp +++ b/tket/test/src/test_Synthesis.cpp @@ -1503,104 +1503,6 @@ SCENARIO("Testing Synthesis OQC") { } } -SCENARIO("Test synthesise_HQS") { - GIVEN("A simple ZXZ chain") { - Circuit circ(1); - circ.add_op(OpType::Rz, 0.3333, {0}); - circ.add_op(OpType::Rx, 1.3333, {0}); - circ.add_op(OpType::Rz, 0.3333, {0}); - REQUIRE(Transforms::synthesise_HQS().apply(circ)); - SliceVec slices = circ.get_slices(); - REQUIRE(circ.get_OpType_from_Vertex(*slices[0].begin()) == OpType::Rz); - REQUIRE(circ.get_OpType_from_Vertex(*slices[1].begin()) == OpType::PhasedX); - Expr first = - (circ.get_Op_ptr_from_Vertex(*slices[0].begin()))->get_params()[0]; - Expr second = - (circ.get_Op_ptr_from_Vertex(*slices[1].begin()))->get_params()[0]; - Expr third = - (circ.get_Op_ptr_from_Vertex(*slices[1].begin()))->get_params()[1]; - REQUIRE(test_equiv_val(first, 0.6666)); - // Two equivalent possibilities for the PhasedX: - bool poss1 = - (test_equiv_val(second, 1.3333) && test_equiv_val(third, 0.3333)); - bool poss2 = - (test_equiv_val(second, 0.6667) && test_equiv_val(third, 1.3333)); - bool ok = poss1 || poss2; - REQUIRE(ok); - } - GIVEN("A 2-qb circuit") { - Circuit circ(2); - circ.add_op(OpType::Z, {0}); - circ.add_op(OpType::X, {0}); - circ.add_op(OpType::Z, {0}); - circ.add_op(OpType::X, {0}); - circ.add_op(OpType::Z, {0}); - circ.add_op(OpType::CX, {0, 1}); - circ.add_op(OpType::Z, {1}); - circ.add_op(OpType::X, {1}); - circ.add_op(OpType::Z, {1}); - circ.add_op(OpType::Rz, 0.3333, {1}); - REQUIRE(Transforms::synthesise_HQS().apply(circ)); - circ.get_slices(); - REQUIRE(circ.n_vertices() == 10); - auto slices = circ.get_slices(); - REQUIRE(slices.size() == 5); - REQUIRE(circ.get_OpType_from_Vertex(slices[4].front()) == OpType::Rz); - REQUIRE(circ.get_OpType_from_Vertex(slices[3].front()) == OpType::PhasedX); - REQUIRE(circ.get_OpType_from_Vertex(slices[2].front()) == OpType::ZZMax); - REQUIRE(circ.get_OpType_from_Vertex(slices[1].front()) == OpType::PhasedX); - REQUIRE(circ.get_OpType_from_Vertex(slices[0].front()) == OpType::Rz); - } - GIVEN("An X-Z-X chain") { - Circuit circ(1); - circ.add_op(OpType::Rx, 1.3333, {0}); - circ.add_op(OpType::Rz, 0.5, {0}); - circ.add_op(OpType::Rx, 0.6666, {0}); - REQUIRE(Transforms::synthesise_HQS().apply(circ)); - auto slices = circ.get_slices(); - REQUIRE(slices.size() == 2); - REQUIRE(circ.get_OpType_from_Vertex(*slices[1].begin()) == OpType::PhasedX); - REQUIRE(circ.get_OpType_from_Vertex(*slices[0].begin()) == OpType::Rz); - } - GIVEN("A perfect CX-Rz(pi/2)-CX phase gadget") { - Circuit circ(2); - circ.add_op(OpType::CX, {0, 1}); - circ.add_op(OpType::Rz, 0.5, {1}); - circ.add_op(OpType::CX, {0, 1}); - REQUIRE(Transforms::synthesise_HQS().apply(circ)); - REQUIRE(circ.get_slices().size() == 1); - } - GIVEN("Something that isn't quite a phase gadget") { - Circuit circ(2); - circ.add_op(OpType::CX, {0, 1}); - circ.add_op(OpType::Rz, 0.499999, {1}); - circ.add_op(OpType::CX, {0, 1}); - REQUIRE(Transforms::synthesise_HQS().apply(circ)); - REQUIRE(circ.get_slices().size() > 3); - - Circuit circ2(2); - circ2.add_op(OpType::CX, {0, 1}); - circ2.add_op(OpType::Rz, 0.500003, {0}); - circ2.add_op(OpType::CX, {0, 1}); - REQUIRE(Transforms::synthesise_HQS().apply(circ2)); - REQUIRE(circ2.get_slices().size() == 1); - REQUIRE( - circ2.get_OpType_from_Vertex(*circ2.get_slices()[0].begin()) == - OpType::Rz); - } - GIVEN("A CRz") { - Circuit circ(2); - circ.add_op(OpType::CRz, 1., {0, 1}); - REQUIRE(Transforms::synthesise_HQS().apply(circ)); - } - GIVEN("A mixed circuit") { - Circuit circ(2, 1); - circ.add_op(OpType::H, {0}); - circ.add_conditional_gate(OpType::CX, {}, {0, 1}, {0}, 0); - REQUIRE_NOTHROW(Transforms::synthesise_HQS().apply(circ)); - } -} - SCENARIO("Test synthesise_UMD") { GIVEN("3 expressions which =0") { Expr a = 0.; @@ -2300,7 +2202,6 @@ SCENARIO("Synthesis with conditional gates") { c.add_conditional_gate(OpType::U1, {0.25}, {1}, {0}, 1); c.add_conditional_gate(OpType::CnRy, {0.25}, {0, 1, 2}, {0, 1}, 0); c.add_measure(2, 2); - check_conditions(SynthesiseHQS(), c); check_conditions(SynthesiseOQC(), c); check_conditions(SynthesiseTK(), c); check_conditions(SynthesiseTket(), c); diff --git a/tket/test/src/test_UnitaryTableau.cpp b/tket/test/src/test_UnitaryTableau.cpp index 67b31fab98..6693ff658d 100644 --- a/tket/test/src/test_UnitaryTableau.cpp +++ b/tket/test/src/test_UnitaryTableau.cpp @@ -16,6 +16,7 @@ #include #include "testutil.hpp" +#include "tket/Circuit/Simulation/CircuitSimulator.hpp" #include "tket/Clifford/UnitaryTableau.hpp" #include "tket/Converters/Converters.hpp" #include "tket/Converters/UnitaryTableauBox.hpp" @@ -246,11 +247,59 @@ SCENARIO("Correct creation of UnitaryTableau") { CHECK(tab0 == tab4); CHECK(tab0 == tab5); } + GIVEN("A single Z gate") { + UnitaryTableau tab0(3); + UnitaryTableau tab1(3); + UnitaryTableau tab2(3); + UnitaryTableau tab3(3); + UnitaryTableau tab4(3); + UnitaryTableau tab5(3); + tab0.apply_gate_at_front(OpType::Z, {Qubit(0)}); + tab1.apply_gate_at_end(OpType::Z, {Qubit(0)}); + tab2.apply_gate_at_front(OpType::S, {Qubit(0)}); + tab2.apply_gate_at_front(OpType::S, {Qubit(0)}); + tab3.apply_gate_at_end(OpType::Sdg, {Qubit(0)}); + tab3.apply_gate_at_end(OpType::Sdg, {Qubit(0)}); + tab4.apply_Z_at_front(Qubit(0)); + tab5.apply_Z_at_end(Qubit(0)); + CHECK(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z)); + CHECK(tab0.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::X, 2)); + CHECK(tab0 == tab1); + CHECK(tab0 == tab2); + CHECK(tab0 == tab3); + CHECK(tab0 == tab4); + CHECK(tab0 == tab5); + } + GIVEN("A single X gate") { + UnitaryTableau tab0(3); + UnitaryTableau tab1(3); + UnitaryTableau tab2(3); + UnitaryTableau tab3(3); + UnitaryTableau tab4(3); + UnitaryTableau tab5(3); + tab0.apply_gate_at_front(OpType::X, {Qubit(0)}); + tab1.apply_gate_at_end(OpType::X, {Qubit(0)}); + tab2.apply_gate_at_front(OpType::V, {Qubit(0)}); + tab2.apply_gate_at_front(OpType::V, {Qubit(0)}); + tab3.apply_gate_at_end(OpType::Vdg, {Qubit(0)}); + tab3.apply_gate_at_end(OpType::Vdg, {Qubit(0)}); + tab4.apply_X_at_front(Qubit(0)); + tab5.apply_X_at_end(Qubit(0)); + CHECK(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z, 2)); + CHECK(tab0.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::X)); + CHECK(tab0 == tab1); + CHECK(tab0 == tab2); + CHECK(tab0 == tab3); + CHECK(tab0 == tab4); + CHECK(tab0 == tab5); + } GIVEN("A single H gate") { UnitaryTableau tab0(3); UnitaryTableau tab1(3); UnitaryTableau tab2(3); UnitaryTableau tab3(3); + UnitaryTableau tab4(3); + UnitaryTableau tab5(3); tab0.apply_gate_at_front(OpType::H, {Qubit(0)}); tab1.apply_gate_at_end(OpType::H, {Qubit(0)}); tab2.apply_gate_at_front(OpType::S, {Qubit(0)}); @@ -259,11 +308,15 @@ SCENARIO("Correct creation of UnitaryTableau") { tab3.apply_gate_at_end(OpType::Vdg, {Qubit(0)}); tab3.apply_gate_at_end(OpType::Sdg, {Qubit(0)}); tab3.apply_gate_at_end(OpType::Vdg, {Qubit(0)}); + tab4.apply_H_at_front(Qubit(0)); + tab5.apply_H_at_end(Qubit(0)); CHECK(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::X)); CHECK(tab0.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z)); CHECK(tab0 == tab1); CHECK(tab0 == tab2); CHECK(tab0 == tab3); + CHECK(tab0 == tab4); + CHECK(tab0 == tab5); } GIVEN("A single CX gate") { UnitaryTableau tab0(3); @@ -297,6 +350,21 @@ SCENARIO("Correct creation of UnitaryTableau") { UnitaryTableau tab = circuit_to_unitary_tableau(circ); UnitaryTableau rev_tab = get_tableau_with_gates_applied_at_front(); REQUIRE(tab == rev_tab); + Eigen::MatrixXcd circ_u = tket_sim::get_unitary(circ); + for (unsigned q = 0; q < 3; ++q) { + CmplxSpMat xq = SpPauliString(Qubit(q), Pauli::X).to_sparse_matrix(3); + Eigen::MatrixXcd xqd = xq; + PauliStabiliser xrow = tab.get_xrow(Qubit(q)); + CmplxSpMat xrowmat = xrow.to_sparse_matrix(3); + Eigen::MatrixXcd xrowmatd = xrowmat; + CHECK((xrowmatd * circ_u * xqd).isApprox(circ_u)); + CmplxSpMat zq = SpPauliString(Qubit(q), Pauli::Z).to_sparse_matrix(3); + Eigen::MatrixXcd zqd = zq; + PauliStabiliser zrow = tab.get_zrow(Qubit(q)); + CmplxSpMat zrowmat = zrow.to_sparse_matrix(3); + Eigen::MatrixXcd zrowmatd = zrowmat; + CHECK((zrowmatd * circ_u * zqd).isApprox(circ_u)); + } } GIVEN("A PI/2 rotation") { Circuit circ = get_test_circ(); @@ -359,6 +427,22 @@ SCENARIO("Synthesis of circuits from UnitaryTableau") { UnitaryTableau res_tab = circuit_to_unitary_tableau(res); REQUIRE(res_tab == tab); } + GIVEN("Additional gate coverage, check unitary") { + Circuit circ(4); + circ.add_op(OpType::ZZMax, {0, 1}); + circ.add_op(OpType::ECR, {2, 3}); + circ.add_op(OpType::ISWAPMax, {1, 2}); + circ.add_op(OpType::noop, {0}); + UnitaryTableau tab = circuit_to_unitary_tableau(circ); + UnitaryTableau rev_tab(4); + rev_tab.apply_gate_at_front(OpType::noop, {Qubit(0)}); + rev_tab.apply_gate_at_front(OpType::ISWAPMax, {Qubit(1), Qubit(2)}); + rev_tab.apply_gate_at_front(OpType::ECR, {Qubit(2), Qubit(3)}); + rev_tab.apply_gate_at_front(OpType::ZZMax, {Qubit(0), Qubit(1)}); + REQUIRE(tab == rev_tab); + Circuit res = unitary_tableau_to_circuit(tab); + REQUIRE(test_unitary_comparison(circ, res, true)); + } } SCENARIO("Correct creation of UnitaryRevTableau") { @@ -418,14 +502,52 @@ SCENARIO("Correct creation of UnitaryRevTableau") { CHECK(tab0 == tab4); CHECK(tab0 == tab5); } + GIVEN("A single Z gate") { + UnitaryRevTableau tab0(3); + UnitaryRevTableau tab1(3); + UnitaryRevTableau tab2(3); + UnitaryRevTableau tab3(3); + tab0.apply_gate_at_end(OpType::Z, {Qubit(0)}); + tab1.apply_gate_at_front(OpType::Z, {Qubit(0)}); + tab2.apply_Z_at_end(Qubit(0)); + tab3.apply_Z_at_front(Qubit(0)); + REQUIRE(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z)); + REQUIRE( + tab0.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::X, 2)); + REQUIRE(tab0 == tab1); + REQUIRE(tab0 == tab2); + REQUIRE(tab0 == tab3); + } + GIVEN("A single X gate") { + UnitaryRevTableau tab0(3); + UnitaryRevTableau tab1(3); + UnitaryRevTableau tab2(3); + UnitaryRevTableau tab3(3); + tab0.apply_gate_at_end(OpType::X, {Qubit(0)}); + tab1.apply_gate_at_front(OpType::X, {Qubit(0)}); + tab2.apply_X_at_end(Qubit(0)); + tab3.apply_X_at_front(Qubit(0)); + REQUIRE( + tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z, 2)); + REQUIRE(tab0.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::X)); + REQUIRE(tab0 == tab1); + REQUIRE(tab0 == tab2); + REQUIRE(tab0 == tab3); + } GIVEN("A single H gate") { UnitaryRevTableau tab0(3); UnitaryRevTableau tab1(3); + UnitaryRevTableau tab2(3); + UnitaryRevTableau tab3(3); tab0.apply_gate_at_end(OpType::H, {Qubit(0)}); tab1.apply_gate_at_front(OpType::H, {Qubit(0)}); + tab2.apply_H_at_end(Qubit(0)); + tab3.apply_H_at_front(Qubit(0)); REQUIRE(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::X)); REQUIRE(tab0.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z)); REQUIRE(tab0 == tab1); + REQUIRE(tab0 == tab2); + REQUIRE(tab0 == tab3); } GIVEN("A single CX gate") { UnitaryRevTableau tab0(3); @@ -538,6 +660,29 @@ SCENARIO("Synthesis of circuits from UnitaryRevTableau") { REQUIRE(test_unitary_comparison(circ, res, true)); UnitaryRevTableau res_tab = circuit_to_unitary_rev_tableau(res); REQUIRE(res_tab == tab); + // Manually call apply_gate methods + UnitaryRevTableau tab_front(2); + tab_front.apply_gate_at_front(OpType::ISWAPMax, {Qubit(0), Qubit(1)}); + tab_front.apply_gate_at_front(OpType::SXdg, {Qubit(1)}); + tab_front.apply_gate_at_front(OpType::SX, {Qubit(0)}); + tab_front.apply_gate_at_front(OpType::ECR, {Qubit(0), Qubit(1)}); + tab_front.apply_gate_at_front(OpType::SXdg, {Qubit(1)}); + tab_front.apply_gate_at_front(OpType::SX, {Qubit(0)}); + tab_front.apply_gate_at_front(OpType::ZZMax, {Qubit(0), Qubit(1)}); + tab_front.apply_gate_at_front(OpType::SXdg, {Qubit(1)}); + tab_front.apply_gate_at_front(OpType::SX, {Qubit(0)}); + REQUIRE(tab == tab_front); + UnitaryRevTableau tab_end(2); + tab_end.apply_gate_at_end(OpType::SX, {Qubit(0)}); + tab_end.apply_gate_at_end(OpType::SXdg, {Qubit(1)}); + tab_end.apply_gate_at_end(OpType::ZZMax, {Qubit(0), Qubit(1)}); + tab_end.apply_gate_at_end(OpType::SX, {Qubit(0)}); + tab_end.apply_gate_at_end(OpType::SXdg, {Qubit(1)}); + tab_end.apply_gate_at_end(OpType::ECR, {Qubit(0), Qubit(1)}); + tab_end.apply_gate_at_end(OpType::SX, {Qubit(0)}); + tab_end.apply_gate_at_end(OpType::SXdg, {Qubit(1)}); + tab_end.apply_gate_at_end(OpType::ISWAPMax, {Qubit(0), Qubit(1)}); + REQUIRE(tab == tab_end); } } diff --git a/tket/test/src/test_json.cpp b/tket/test/src/test_json.cpp index 86466f172a..be596579fb 100644 --- a/tket/test/src/test_json.cpp +++ b/tket/test/src/test_json.cpp @@ -693,7 +693,7 @@ SCENARIO("Test Circuit serialization") { SCENARIO("Test device serializations") { GIVEN("Architecture") { - Architecture arc({{0, 1}, {1, 2}}); + Architecture arc({{Node(0), Node(1)}, {Node(1), Node(2)}}); nlohmann::json j_arc = arc; Architecture loaded_arc = j_arc.get(); CHECK(arc == loaded_arc); @@ -918,7 +918,6 @@ SCENARIO("Test compiler pass serializations") { COMPPASSJSONTEST(RebaseTket, RebaseTket()) COMPPASSJSONTEST(RebaseUFR, RebaseUFR()) COMPPASSJSONTEST(RemoveRedundancies, RemoveRedundancies()) - COMPPASSJSONTEST(SynthesiseHQS, SynthesiseHQS()) COMPPASSJSONTEST(SynthesiseTK, SynthesiseTK()) COMPPASSJSONTEST(SynthesiseTket, SynthesiseTket()) COMPPASSJSONTEST(SynthesiseOQC, SynthesiseOQC())