From af9015ce0fee7c1df7a6f4b858e9cd7994a80017 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Thu, 16 Nov 2023 13:33:52 +0000 Subject: [PATCH 01/35] [infra] Drop support for MacOS 11 (#1130) --- .github/workflows/release.yml | 14 +++++++------- conan-profiles/macos-11 | 8 -------- pytket/docs/changelog.rst | 4 ++++ 3 files changed, 11 insertions(+), 15 deletions(-) delete mode 100644 conan-profiles/macos-11 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bf0b7b8b44..ef92819e26 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,7 +73,7 @@ jobs: build_macos_wheels: name: Build macos wheels - runs-on: macos-11 + runs-on: macos-12 strategy: matrix: python-version: ['3.9', '3.10', '3.11'] @@ -92,7 +92,7 @@ jobs: run: | conan profile detect DEFAULT_PROFILE_PATH=`conan profile path default` - PROFILE_PATH=./conan-profiles/macos-11 + PROFILE_PATH=./conan-profiles/macos-12 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 @@ -103,8 +103,8 @@ jobs: conan create recipes/pybind11 conan create recipes/pybind11_json/all --version 0.2.13 cd pytket - # Ensure wheels are compatible with MacOS 10.14 and later: - export WHEEL_PLAT_NAME=macosx_10_14_x86_64 + # Ensure wheels are compatible with MacOS 12.0 and later: + export WHEEL_PLAT_NAME=macosx_12_0_x86_64 pip install -U pip build delocate python -m build delocate-wheel -v -w "$GITHUB_WORKSPACE/wheelhouse/" "dist/pytket-"*".whl" @@ -149,8 +149,8 @@ jobs: conan create recipes/pybind11 conan create recipes/pybind11_json/all --version 0.2.13 cd pytket - # Ensure wheels are compatible with MacOS 11.0 and later: - export WHEEL_PLAT_NAME=macosx_11_0_arm64 + # 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 build delocate-wheel -v -w "$GITHUB_WORKSPACE/wheelhouse/" "dist/pytket-"*".whl" @@ -268,7 +268,7 @@ jobs: needs: build_macos_wheels strategy: matrix: - os: ['macos-11', 'macos-12'] + os: ['macos-12', 'macos-13'] python-version: ['3.9', '3.10', '3.11'] runs-on: ${{ matrix.os }} steps: diff --git a/conan-profiles/macos-11 b/conan-profiles/macos-11 deleted file mode 100644 index a275dacb59..0000000000 --- a/conan-profiles/macos-11 +++ /dev/null @@ -1,8 +0,0 @@ -[settings] -arch=x86_64 -build_type=Release -compiler=apple-clang -compiler.cppstd=gnu17 -compiler.libcxx=libc++ -compiler.version=13 -os=Macos diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index e38e7c3d31..7c1268dbdc 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -21,6 +21,10 @@ Fixes: length 2. * Fix incorrect controlled ``ConjugationBox`` handling. +General: + +* Drop support for MacOS 11. + 1.21.0 (October 2023) --------------------- From 8a3da30e1f68fa11e309f4bfa4bba5a1912af02e Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Mon, 20 Nov 2023 15:10:06 +0000 Subject: [PATCH 02/35] [infra] Bugfix/1132 update nix macos compiler (#1133) * Updated nixpkgs to the latest revision, which includes clang16 as the default macos compiler * Updated copy path for python extras to take the python version into account, rather than hardcoding 3.10 (as the prior commit updated to python3.11) --------- Co-authored-by: Jake Arkinstall --- flake.lock | 6 +++--- nix-support/pytket.nix | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/flake.lock b/flake.lock index 4ba12b3083..6f2399adf4 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1697158538, - "narHash": "sha256-TR02XPre2EcwU1kT4WXZWJOBi/B8C7P1xoywOUGkdDk=", + "lastModified": 1700484102, + "narHash": "sha256-yGPHhYBH8KZu70gZvHRBvIzN0Z9dlwXX7u3hFFiGevA=", "owner": "nixos", "repo": "nixpkgs", - "rev": "ee176b5fbb680864dcf80f9ca3f6f134d81331d8", + "rev": "48a753219ede79d448cd0aa3bbf29d29fcbab8c9", "type": "github" }, "original": { diff --git a/nix-support/pytket.nix b/nix-support/pytket.nix index 9489ea5589..ac6fa12a21 100644 --- a/nix-support/pytket.nix +++ b/nix-support/pytket.nix @@ -74,10 +74,10 @@ in { # these directories aren't copied by setup.py, so we do it manually cp -r ${ ../pytket/pytket/circuit/display/js - } $out/lib/python3.10/site-packages/pytket/circuit/display/js; + } $out/lib/python${super.python3.pythonVersion}/site-packages/pytket/circuit/display/js; cp -r ${ ../pytket/pytket/circuit/display/static - } $out/lib/python3.10/site-packages/pytket/circuit/display/static; + } $out/lib/python${super.python3.pythonVersion}/site-packages/pytket/circuit/display/static; ''; checkInputs = with super.python3.pkgs; [ pytest From 6cf0ed52499de51c5b26be91c9b6917a0d806bc0 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Tue, 21 Nov 2023 12:49:46 +0000 Subject: [PATCH 03/35] [infra] Pin to pybind11-stubgen 2.3.6. (#1136) --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 323c26c818..3cadc33dd5 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -396,7 +396,7 @@ jobs: if: matrix.os == 'macos-13-xlarge' && (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') run: | python${{ matrix.python-version }} -m pip install -U mypy - python${{ matrix.python-version }} -m pip install -U pybind11-stubgen + python${{ matrix.python-version }} -m pip install pybind11-stubgen==2.3.6 # https://github.com/CQCL/tket/issues/1135 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 From 437427d1bf483661a94b934a38fb59cc4f786e6c Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Tue, 21 Nov 2023 14:56:43 +0000 Subject: [PATCH 04/35] Update emulator info and fix typo (#1134) * update emulator info and fix typo * rename extensions_index.rst to extensions.rst --------- Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- pytket/docs/{extensions_index.rst => extensions.rst} | 2 +- pytket/docs/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename pytket/docs/{extensions_index.rst => extensions.rst} (98%) diff --git a/pytket/docs/extensions_index.rst b/pytket/docs/extensions.rst similarity index 98% rename from pytket/docs/extensions_index.rst rename to pytket/docs/extensions.rst index 8c26c94475..3ca7c1a09f 100644 --- a/pytket/docs/extensions_index.rst +++ b/pytket/docs/extensions.rst @@ -63,7 +63,7 @@ Emulators `IBMQEmulatorBackend`_ - A backend which uses the `AerBackend `_ to emulate the behavior of IBMQBackend. `QuantinuumBackend `_ -- The QuantinuumBackend has two available emulators namely H1-1E and H1-2E. These are device specific emulators for the H1-1 and H1-2 devices. These emualtors run remotely on a server. +- The QuantinuumBackend has two available emulators namely H1-1E and H2-1E. These are device specific emulators for the H1-1 and H2-1 devices. These emulators run remotely on a server. Statevector Simulators ---------------------- diff --git a/pytket/docs/index.rst b/pytket/docs/index.rst index 9bbf64265f..1c1a953337 100644 --- a/pytket/docs/index.rst +++ b/pytket/docs/index.rst @@ -87,7 +87,7 @@ Licensed under the `Apache 2 License - extensions_index.rst + extensions.rst Example notebooks .. toctree:: From 49db4be3a51846b9afb0c7e11a5afcb4d1304b8d Mon Sep 17 00:00:00 2001 From: cqc-melf <70640934+cqc-melf@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:59:59 +0000 Subject: [PATCH 05/35] add link to tket website (#1137) --- pytket/docs/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/pytket/docs/index.rst b/pytket/docs/index.rst index 1c1a953337..2a5b9e1f85 100644 --- a/pytket/docs/index.rst +++ b/pytket/docs/index.rst @@ -86,6 +86,7 @@ Licensed under the `Apache 2 License Manual extensions.rst Example notebooks From 8d84e19387b64019ba0a3f8f4314dfeb82d35d76 Mon Sep 17 00:00:00 2001 From: Jake Arkinstall <65358059+jake-arkinstall@users.noreply.github.com> Date: Wed, 22 Nov 2023 09:16:45 +0000 Subject: [PATCH 06/35] [feature] Add mypy support to nix builds of pytket (#1138) * Nix: added pyi interface files to pytket output, and added mypy checks on pytket and the tests into the checkPhase of the pytket nix derivation. Added result directory to .gitignore incase anyone runs nix build in the source directory. * Removed unnecessary print statement from setup.py in nix builds * Added space between loops in setup.py to keep the linter happy * Additional lint fix --------- Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- .gitignore | 3 +++ nix-support/pytket.nix | 7 +++++++ pytket/setup.py | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/.gitignore b/.gitignore index 90eb7af76d..69e53f7f91 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ pytket/tests/qasm_test_files/testout6.qasm # IDEs .idea .vscode + +#nix +result diff --git a/nix-support/pytket.nix b/nix-support/pytket.nix index ac6fa12a21..6368ac7a1f 100644 --- a/nix-support/pytket.nix +++ b/nix-support/pytket.nix @@ -55,6 +55,7 @@ in { cp -r ${../pytket/pytket} pytket; cp ${../pytket/package.md} package.md; cp -r ${../schemas} schemas; + cp -r ${../pytket/mypy.ini} mypy.ini; # The usual build depends on setuptools-scm to extract the version. # We have already extracted the version within nix, so we can simply @@ -80,6 +81,7 @@ in { } $out/lib/python${super.python3.pythonVersion}/site-packages/pytket/circuit/display/static; ''; checkInputs = with super.python3.pkgs; [ + mypy pytest pytest-cov pytest-benchmark @@ -90,6 +92,11 @@ in { ] ++ [jsonschema-4180]; checkPhase = '' export HOME=$TMPDIR; + + # run mypy + python -m mypy --config-file=mypy.ini --no-incremental -p pytket -p test_root.tests; + + # run tests chmod 700 $TMPDIR/test_root/tests/qasm_test_files; cd test_root/tests; python -m pytest -s . diff --git a/pytket/setup.py b/pytket/setup.py index b26819b448..f4280641b7 100755 --- a/pytket/setup.py +++ b/pytket/setup.py @@ -143,6 +143,10 @@ def run(self): if not os.path.isdir(libpath): shutil.copy(libpath, extdir) + for interface_file in os.listdir("pytket/_tket"): + if interface_file.endswith(".pyi") or interface_file.endswith(".py"): + shutil.copy(os.path.join("pytket/_tket", interface_file), extdir) + plat_name = os.getenv("WHEEL_PLAT_NAME") From fe000880e8a6d86651925a28b2b1433fa44ea745 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Thu, 23 Nov 2023 14:43:38 +0000 Subject: [PATCH 07/35] Deprecate `SynthesiseHQS` (#1143) --- pytket/binders/passes.cpp | 13 +++++++++++-- pytket/docs/changelog.rst | 7 +++++++ pytket/pytket/_tket/passes.pyi | 2 +- pytket/tests/predicates_test.py | 11 +++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/pytket/binders/passes.cpp b/pytket/binders/passes.cpp index a7742b4eca..1228d0ec7b 100644 --- a/pytket/binders/passes.cpp +++ b/pytket/binders/passes.cpp @@ -14,6 +14,8 @@ #include +#include + #include "binder_json.hpp" #include "tket/ArchAwareSynth/SteinerForest.hpp" #include "tket/Mapping/LexiLabelling.hpp" @@ -503,9 +505,16 @@ PYBIND11_MODULE(passes, m) { "When merging rotations with the same op group name, the merged " "operation keeps the same name."); m.def( - "SynthesiseHQS", &SynthesiseHQS, + "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."); + "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."); diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 03a0a465c2..1f06f1fefd 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -1,6 +1,13 @@ Changelog ========= +Unreleased +---------- + +Deprecations: + +* Deprecate ``SynthesiseHQS`` pass. + 1.22.0 (November 2023) ---------------------- diff --git a/pytket/pytket/_tket/passes.pyi b/pytket/pytket/_tket/passes.pyi index 063ab5b4b1..a45c9cbd82 100644 --- a/pytket/pytket/_tket/passes.pyi +++ b/pytket/pytket/_tket/passes.pyi @@ -583,7 +583,7 @@ def SquashTK1() -> BasePass: """ def SynthesiseHQS() -> BasePass: """ - Optimises and converts a circuit consisting of CX and single-qubit gates into one containing only ZZMax, PhasedX, Rz and Phase. + 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: """ diff --git a/pytket/tests/predicates_test.py b/pytket/tests/predicates_test.py index 101618a85d..a331755443 100644 --- a/pytket/tests/predicates_test.py +++ b/pytket/tests/predicates_test.py @@ -13,6 +13,7 @@ # limitations under the License. import sympy +from pytket import logging from pytket.circuit import ( Circuit, OpType, @@ -69,6 +70,7 @@ FlattenRelabelRegistersPass, RoundAngles, PeepholeOptimise2Q, + SynthesiseHQS, ) from pytket.predicates import ( GateSetPredicate, @@ -967,6 +969,15 @@ 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) + + if __name__ == "__main__": test_predicate_generation() test_compilation_unit_generation() From b16eb3cf281fe7d2b92b7b18b67177f0442e0387 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Thu, 23 Nov 2023 15:24:24 +0000 Subject: [PATCH 08/35] [test] Unskip test on Windows. (#1144) --- pytket/tests/measurement_setup_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pytket/tests/measurement_setup_test.py b/pytket/tests/measurement_setup_test.py index 8b6afea199..d642ab6b4c 100644 --- a/pytket/tests/measurement_setup_test.py +++ b/pytket/tests/measurement_setup_test.py @@ -63,8 +63,6 @@ def test_reduction() -> None: assert measurements.verify() -# readouterr() doesn't seem to work correctly on Windows, so skip. TODO investigate. -@pytest.mark.skipif(platform.system() == "Windows", reason="issues with readouterr()") def test_error_logging(capfd: Any) -> None: ms = MeasurementSetup() circ = Circuit(2, 2) From a5e82bddc395fb2ffcf76dacf0334ba6a872e61a Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Fri, 24 Nov 2023 09:50:41 +0000 Subject: [PATCH 09/35] [doc] Replace links to the pytket-extensions repo. (#1145) --- README.md | 2 +- pytket/docs/extensions.rst | 4 ++-- pytket/docs/getting_started.rst | 4 ++-- pytket/docs/index.rst | 4 ++-- pytket/package.md | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1cfa828e9e..43a3e519a1 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ selection of example notebooks, please follow the links from the In addition to the core pytket package there are pytket extension modules which allow pytket to interface with quantum devices and simulators. Some extensions also provide interoperability with other software libraries such as qiskit, cirq and pennylane. -For a list of available pytket extensions see the [extensions index page](https://cqcl.github.io/pytket-extensions/api/index.html). +For a list of available pytket extensions see the [extensions index page](https://tket.quantinuum.com/extensions/). These extensions are installed as separate python packages and the source code for each extension lives in its own github repository. diff --git a/pytket/docs/extensions.rst b/pytket/docs/extensions.rst index 3ca7c1a09f..751cc9a9c7 100644 --- a/pytket/docs/extensions.rst +++ b/pytket/docs/extensions.rst @@ -132,7 +132,7 @@ Other pytket-aqt pytket-braket pytket-cirq - pytket-ionq + pytket-ionq pytket-iqm pytket-pennylane pytket-projectq @@ -140,7 +140,7 @@ Other pytket-pysimplex pytket-pyzx pytket-qir - pytket-qiskit + pytket-qiskit pytket-qsharp pytket-quantinuum pytket-cutensornet diff --git a/pytket/docs/getting_started.rst b/pytket/docs/getting_started.rst index a6ea8e88b1..d2a1738cfd 100644 --- a/pytket/docs/getting_started.rst +++ b/pytket/docs/getting_started.rst @@ -21,7 +21,7 @@ those using an older version of pytket, keep up to date by installing with the There are separate packages for managing the interoperability between pytket and other quantum software packages which can also be installed via PyPI. For details of these, see the -`pytket-extensions `_ documentation. +`pytket extensions `_ documentation. The quantum circuit is an abstraction of computation using quantum resources, @@ -72,7 +72,7 @@ for an extensive tutorial on pytket, providing a gentle introduction to its features and how to run circuits on backend devices, with worked examples. In pytket there is also a generic :py:class:`Backend` interface. This represents a connection to a quantum device or simulator. -It's possible to run circuits on platforms from different providers through the `extension modules `_. +It's possible to run circuits on platforms from different providers through the `extension modules `_. :: diff --git a/pytket/docs/index.rst b/pytket/docs/index.rst index 2a5b9e1f85..fdf9eb77c8 100644 --- a/pytket/docs/index.rst +++ b/pytket/docs/index.rst @@ -2,7 +2,7 @@ pytket ====== ``pytket`` is a python module for interfacing with tket, a quantum computing toolkit and optimising compiler developed by `Quantinuum`_. We currently support circuits and device architectures from -`numerous providers `_, allowing the +`numerous providers `_, allowing the tket tools to be used in conjunction with projects on their platforms. ``pytket`` is available for Python 3.9, 3.10 and 3.11, on Linux, MacOS and @@ -38,7 +38,7 @@ To install the ``pytket-quantinuum`` package use the following command. pip install pytket-quantinuum The extensions supported by tket are described -`here `_. +`here `_. How to cite ~~~~~~~~~~~ diff --git a/pytket/package.md b/pytket/package.md index e3498fd6b5..77dcad1928 100644 --- a/pytket/package.md +++ b/pytket/package.md @@ -16,7 +16,7 @@ To install the pytket extension modules add a hyphen and the extension name to t `` pip install pytket-quantinuum `` -For a list of pytket extensions see this page: https://cqcl.github.io/pytket-extensions/api/index.html. +For a list of pytket extensions see this page: https://tket.quantinuum.com/extensions/. _Warning._ There is a [known issue](https://github.com/CQCL/tket/issues/926) with installing pytket in a conda environment on MacOS: you may not be able to From 1a02f5d2d40e25f5bde4e204be3cc28ddd04f3d4 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Fri, 24 Nov 2023 14:49:46 +0000 Subject: [PATCH 10/35] Fix links to extensions page. (#1146) --- README.md | 2 +- pytket/docs/getting_started.rst | 4 ++-- pytket/docs/index.rst | 4 ++-- pytket/package.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 43a3e519a1..b851b11588 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ selection of example notebooks, please follow the links from the In addition to the core pytket package there are pytket extension modules which allow pytket to interface with quantum devices and simulators. Some extensions also provide interoperability with other software libraries such as qiskit, cirq and pennylane. -For a list of available pytket extensions see the [extensions index page](https://tket.quantinuum.com/extensions/). +For a list of available pytket extensions see the [extensions index page](https://tket.quantinuum.com/api-docs/extensions). These extensions are installed as separate python packages and the source code for each extension lives in its own github repository. diff --git a/pytket/docs/getting_started.rst b/pytket/docs/getting_started.rst index d2a1738cfd..8a05896329 100644 --- a/pytket/docs/getting_started.rst +++ b/pytket/docs/getting_started.rst @@ -21,7 +21,7 @@ those using an older version of pytket, keep up to date by installing with the There are separate packages for managing the interoperability between pytket and other quantum software packages which can also be installed via PyPI. For details of these, see the -`pytket extensions `_ documentation. +`pytket extensions `_ documentation. The quantum circuit is an abstraction of computation using quantum resources, @@ -72,7 +72,7 @@ for an extensive tutorial on pytket, providing a gentle introduction to its features and how to run circuits on backend devices, with worked examples. In pytket there is also a generic :py:class:`Backend` interface. This represents a connection to a quantum device or simulator. -It's possible to run circuits on platforms from different providers through the `extension modules `_. +It's possible to run circuits on platforms from different providers through the `extension modules `_. :: diff --git a/pytket/docs/index.rst b/pytket/docs/index.rst index fdf9eb77c8..c5966e12f5 100644 --- a/pytket/docs/index.rst +++ b/pytket/docs/index.rst @@ -2,7 +2,7 @@ pytket ====== ``pytket`` is a python module for interfacing with tket, a quantum computing toolkit and optimising compiler developed by `Quantinuum`_. We currently support circuits and device architectures from -`numerous providers `_, allowing the +`numerous providers `_, allowing the tket tools to be used in conjunction with projects on their platforms. ``pytket`` is available for Python 3.9, 3.10 and 3.11, on Linux, MacOS and @@ -38,7 +38,7 @@ To install the ``pytket-quantinuum`` package use the following command. pip install pytket-quantinuum The extensions supported by tket are described -`here `_. +`here `_. How to cite ~~~~~~~~~~~ diff --git a/pytket/package.md b/pytket/package.md index 77dcad1928..b2fe2a32c0 100644 --- a/pytket/package.md +++ b/pytket/package.md @@ -16,7 +16,7 @@ To install the pytket extension modules add a hyphen and the extension name to t `` pip install pytket-quantinuum `` -For a list of pytket extensions see this page: https://tket.quantinuum.com/extensions/. +For a list of pytket extensions see this page: https://tket.quantinuum.com/api-docs/extensions. _Warning._ There is a [known issue](https://github.com/CQCL/tket/issues/926) with installing pytket in a conda environment on MacOS: you may not be able to From d1165481aa96f39b7852f01b9d44bbc0187cb06f Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:45:27 +0000 Subject: [PATCH 11/35] Remove `pytket-ionq` backend from index and add sidebar links (#1148) * remove pytket-ionq from the extensions index page * mention qulacs density matrix simulator * update sidebar links to docs pages --- pytket/docs/extensions.rst | 11 ++++++----- pytket/docs/index.rst | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pytket/docs/extensions.rst b/pytket/docs/extensions.rst index 751cc9a9c7..4975510211 100644 --- a/pytket/docs/extensions.rst +++ b/pytket/docs/extensions.rst @@ -36,9 +36,6 @@ QPUs `IBMQBackend `_ - A backend for running circuits on remote IBMQ devices. -`IonQBackend `_ -- A backend for running circuits on remote IONQ devices. - `ForestBackend `_ - A backend for running circuits on remote Rigetti devices. @@ -95,6 +92,10 @@ Density Matrix Simulators `CirqDensityMatrixSimBackend `_ - Backend for Cirq density matrix simulator density_matrix return. +`QulacsBackend`_ - This has a configurable density matrix simulation option. + +Use ``QulacsBackend(result_type="density_matrix")``. + Clifford Simulators ------------------- @@ -132,7 +133,6 @@ Other pytket-aqt pytket-braket pytket-cirq - pytket-ionq pytket-iqm pytket-pennylane pytket-projectq @@ -156,4 +156,5 @@ Other .. _ForestStateBackend: https://tket.quantinuum.com/extensions/pytket-pyquil/api/api.html#pytket.extensions.pyquil.ForestStateBackend .. _AerUnitaryBackend: https://tket.quantinuum.com/extensions/pytket-qiskit/api/api.html#pytket.extensions.qiskit.AerUnitaryBackend .. _CirqDensityMatrixSampleBackend: https://tket.quantinuum.com/extensions/pytket-cirq/api/api.html#pytket.extensions.cirq.CirqDensityMatrixSampleBackend -.. _SimplexBackend: https://tket.quantinuum.com/extensions/pytket-simplex/api.html#pytket.extensions.pysimplex.SimplexBackend \ No newline at end of file +.. _SimplexBackend: https://tket.quantinuum.com/extensions/pytket-simplex/api.html#pytket.extensions.pysimplex.SimplexBackend +.. _QulacsBackend: https://tket.quantinuum.com/extensions/pytket-qulacs/api.html#pytket.extensions.qulacs.QulacsBackend \ No newline at end of file diff --git a/pytket/docs/index.rst b/pytket/docs/index.rst index c5966e12f5..0af927292b 100644 --- a/pytket/docs/index.rst +++ b/pytket/docs/index.rst @@ -75,7 +75,7 @@ Licensed under the `Apache 2 License - Manual - extensions.rst + pytket API docs + extensions.rst + Manual Example notebooks + TKET website .. toctree:: :caption: API Reference: From 300c9303fda9384d21f0c32248cb1ff7def68949 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:02:58 +0000 Subject: [PATCH 12/35] [feature] Error-corrected unitary multiplication (#1149) --- pytket/conanfile.py | 2 +- pytket/docs/changelog.rst | 6 +++ tket/conanfile.py | 2 +- tket/include/tket/Utils/MatrixAnalysis.hpp | 44 +++++++++++++++++++++ tket/src/Circuit/CircUtils.cpp | 13 +++--- tket/test/src/Utils/test_MatrixAnalysis.cpp | 36 +++++++++++++++++ 6 files changed, 95 insertions(+), 8 deletions(-) diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 756035975c..9a2c1ff5d4 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.68@tket/stable") + self.requires("tket/1.2.69@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") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 1f06f1fefd..2923bf2b8c 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -8,6 +8,12 @@ Deprecations: * Deprecate ``SynthesiseHQS`` pass. +Fixes: + +* Ensure that squashing long sequences of gates via unitary multiplication does + not produce non-unitary results due to rounding errors. + + 1.22.0 (November 2023) ---------------------- diff --git a/tket/conanfile.py b/tket/conanfile.py index 9193b7cacb..6052d4bb09 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.68" + version = "1.2.69" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/include/tket/Utils/MatrixAnalysis.hpp b/tket/include/tket/Utils/MatrixAnalysis.hpp index dc460d251b..ba182b8679 100644 --- a/tket/include/tket/Utils/MatrixAnalysis.hpp +++ b/tket/include/tket/Utils/MatrixAnalysis.hpp @@ -15,6 +15,7 @@ #pragma once #include +#include #include #include "EigenConfig.hpp" @@ -177,4 +178,47 @@ bool in_weyl_chamber(const std::array &k); */ Eigen::Matrix2cd nth_root(const Eigen::Matrix2cd &u, unsigned long long n); +template +static inline MatrixT clamp_to_unitary(const MatrixT &A) { + if (is_unitary(A)) { + return A; + } else { + tket_log()->warn( + "Non-unitary product of matrices assumed unitary: " + "presuming rounding error and applying correction."); + // Fact: if A is an arbitrary complex matrix, and + // A = U * S * V^+ + // is its singular-value decomposition, then + // U * V^+ + // is the unitary matrix closest to A in the Frobenius norm. + // See e.g. https://math.stackexchange.com/a/2215371 for a proof. + Eigen::JacobiSVD svd( + A, Eigen::DecompositionOptions::ComputeFullU | + Eigen::DecompositionOptions::ComputeFullV); + return svd.matrixU() * svd.matrixV().adjoint(); + } +} + +/** + * Compute the product of two unitary matrices, with error correction. + * + * The arguments are assumed to be unitary. + */ +template +MatrixT unitary_product2(const MatrixT &U, const MatrixT &V) { + MatrixT A = U * V; + return clamp_to_unitary(A); +} + +/** + * Compute the product of three unitary matrices, with error correction. + * + * The arguments are assumed to be unitary. + */ +template +MatrixT unitary_product3(const MatrixT &U, const MatrixT &V, const MatrixT &W) { + MatrixT A = U * V * W; + return clamp_to_unitary(A); +} + } // namespace tket diff --git a/tket/src/Circuit/CircUtils.cpp b/tket/src/Circuit/CircUtils.cpp index 379882104b..bfac83d4ec 100644 --- a/tket/src/Circuit/CircUtils.cpp +++ b/tket/src/Circuit/CircUtils.cpp @@ -57,7 +57,7 @@ Eigen::Matrix2cd get_matrix_from_circ(const Circuit &circ) { if (N == 2) return factor * Eigen::Matrix2cd::Identity(); Eigen::Matrix2cd m = get_matrix(circ, qpath[N - 2]); for (unsigned x = N - 3; x >= 1; --x) { - m = m * get_matrix(circ, qpath[x]); + m = unitary_product2(m, get_matrix(circ, qpath[x])); } return factor * m; } @@ -138,7 +138,7 @@ Eigen::Matrix4cd get_matrix_from_2qb_circ(const Circuit &circ) { SliceVec slices = circ.get_slices(); for (const Slice &s : slices) { for (const Vertex &v : s) { - m = v_to_op[v] * m; + m = unitary_product2(v_to_op[v], m); } } return std::exp(i_ * PI * eval_expr(circ.get_phase()).value()) * m; @@ -188,7 +188,8 @@ Circuit two_qubit_canonical(const Eigen::Matrix4cd &U, OpType target_2qb_gate) { OpType::TK1, {angles_q1.begin(), angles_q1.end() - 1}, {1}); // this fixes phase if decomposition is exact - Eigen::Matrix4cd reminder = get_matrix_from_2qb_circ(result).adjoint() * U; + Eigen::Matrix4cd V = get_matrix_from_2qb_circ(result).adjoint(); + Eigen::Matrix4cd reminder = unitary_product2(V, U); const Complex phase = reminder(0, 0); // reminder = phase * I result.add_phase(arg(phase) / PI); return result; @@ -401,8 +402,8 @@ Circuit with_TK2(Gate_ptr op) { {angles_K2a[0], angles_K2a[1], angles_K2a[2], 0}), get_matrix_from_tk1_angles( {angles_K2b[0], angles_K2b[1], angles_K2b[2], 0})); - Eigen::Matrix4cd V = V_K1 * V_A * V_K2; - Eigen::Matrix4cd R = V.adjoint() * U; + Eigen::Matrix4cd V_adj = unitary_product3(V_K1, V_A, V_K2).adjoint(); + Eigen::Matrix4cd R = unitary_product2(V_adj, U); const Complex phase = R(0, 0); // R = phase * I c.add_phase(arg(phase) / PI); @@ -907,7 +908,7 @@ struct CnGateBlock { Eigen::Matrix2cd get_target_unitary() const { Eigen::Matrix2cd m = Eigen::Matrix2cd::Identity(); for (const Op_ptr &op : ops) { - m = get_target_op_matrix(op) * m; + m = unitary_product2(get_target_op_matrix(op), m); } return m; } diff --git a/tket/test/src/Utils/test_MatrixAnalysis.cpp b/tket/test/src/Utils/test_MatrixAnalysis.cpp index d9f3c80649..e6a47c0672 100644 --- a/tket/test/src/Utils/test_MatrixAnalysis.cpp +++ b/tket/test/src/Utils/test_MatrixAnalysis.cpp @@ -112,5 +112,41 @@ SCENARIO("Test nth root of a 2x2 unitary") { } } } + +SCENARIO("Test unitary product") { + GIVEN("Two perturbed unitaries") { + Eigen::Matrix4cd U = random_unitary(4, 0); + U(1, 2) += 0.1; + Eigen::Matrix4cd V = random_unitary(4, 1); + V(3, 0) -= 0.1; + Eigen::Matrix4cd UV = U * V; + Eigen::Matrix4cd X = unitary_product2(U, V); + REQUIRE(!is_unitary(UV)); + REQUIRE(is_unitary(X)); + double d = (X - UV).squaredNorm(); + for (int i = 0; i < 10; i++) { + Eigen::Matrix4cd Y = random_unitary(4, 2 + i); + CHECK((Y - UV).squaredNorm() >= d); + } + } + GIVEN("Three perturbed unitaries") { + Eigen::Matrix4cd U = random_unitary(4, 12); + U(1, 2) += 0.1; + Eigen::Matrix4cd V = random_unitary(4, 13); + V(3, 0) -= 0.1; + Eigen::Matrix4cd W = random_unitary(4, 14); + W(2, 2) += 0.1; + Eigen::Matrix4cd UVW = U * V * W; + Eigen::Matrix4cd X = unitary_product3(U, V, W); + REQUIRE(!is_unitary(UVW)); + REQUIRE(is_unitary(X)); + double d = (X - UVW).squaredNorm(); + for (int i = 0; i < 10; i++) { + Eigen::Matrix4cd Y = random_unitary(4, 15 + i); + CHECK((Y - UVW).squaredNorm() >= d); + } + } +} + } // namespace test_MatrixAnalysis } // namespace tket From f7fda88a3a23c4d633dbfbeca5bfb02a6c9cceee Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Wed, 29 Nov 2023 15:36:14 +0000 Subject: [PATCH 13/35] [infra] Update `nlohmann_json` to 3.11.3 (#1151) --- .github/workflows/build-without-conan.yml | 2 +- build-without-conan.md | 2 +- pytket/conanfile.py | 4 ++-- recipes/pybind11_json/all/conanfile.py | 2 +- tket/conanfile.py | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-without-conan.yml b/.github/workflows/build-without-conan.yml index 48ab2bb313..faa7aa4264 100644 --- a/.github/workflows/build-without-conan.yml +++ b/.github/workflows/build-without-conan.yml @@ -63,7 +63,7 @@ jobs: - name: Install nlohmann_json run: | cd ${TMP_DIR} - wget https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz + wget https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz tar xvf json.tar.xz cd json/ mkdir build diff --git a/build-without-conan.md b/build-without-conan.md index 21b93a8d4e..7e2dc7128f 100644 --- a/build-without-conan.md +++ b/build-without-conan.md @@ -77,7 +77,7 @@ cmake --install . ``` cd ${TMP_DIR} -wget https://github.com/nlohmann/json/releases/download/v3.11.2/json.tar.xz +wget https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz tar xvf json.tar.xz cd json/ mkdir build diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 9a2c1ff5d4..fb043f58f0 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.69@tket/stable") + self.requires("tket/1.2.70@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") @@ -41,5 +41,5 @@ def requirements(self): self.requires("symengine/0.11.1") self.requires("gmp/6.2.1") self.requires("pybind11/2.11.1") - self.requires("nlohmann_json/3.11.2") + self.requires("nlohmann_json/3.11.3") self.requires("pybind11_json/0.2.13") diff --git a/recipes/pybind11_json/all/conanfile.py b/recipes/pybind11_json/all/conanfile.py index 241902054c..cb5a418e59 100644 --- a/recipes/pybind11_json/all/conanfile.py +++ b/recipes/pybind11_json/all/conanfile.py @@ -28,7 +28,7 @@ def package_id(self): self.info.clear() def requirements(self): - self.requires("nlohmann_json/3.11.2") + self.requires("nlohmann_json/3.11.3") self.requires("pybind11/2.11.1") def source(self): diff --git a/tket/conanfile.py b/tket/conanfile.py index 6052d4bb09..6346140ec8 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.69" + version = "1.2.70" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" @@ -114,7 +114,7 @@ def requirements(self): self.requires("boost/1.83.0", transitive_headers=True) self.requires("symengine/0.11.1", transitive_headers=True) self.requires("eigen/3.4.0", transitive_headers=True) - self.requires("nlohmann_json/3.11.2", transitive_headers=True) + self.requires("nlohmann_json/3.11.3", transitive_headers=True) self.requires("tklog/0.3.3@tket/stable") self.requires("tkassert/0.3.4@tket/stable", transitive_headers=True) self.requires("tkrng/0.3.3@tket/stable") From 24cec655496fd7a5b06a72e6b9b6b6d950bca926 Mon Sep 17 00:00:00 2001 From: yao-cqc <75305462+yao-cqc@users.noreply.github.com> Date: Thu, 30 Nov 2023 10:50:01 +0000 Subject: [PATCH 14/35] make architecture field optional (#1106) --- pytket/pytket/backends/backendinfo.py | 24 +++++++++++++++--------- pytket/tests/strategies.py | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/pytket/pytket/backends/backendinfo.py b/pytket/pytket/backends/backendinfo.py index 846c0bf027..f2ee7a0dbd 100644 --- a/pytket/pytket/backends/backendinfo.py +++ b/pytket/pytket/backends/backendinfo.py @@ -144,7 +144,7 @@ class BackendInfo: :param name: Class name of the backend. :param device_name: Name of the device. :param version: Pytket-extension version installed when creating object. - :param architecture: Device connectivity. + :param architecture: Optional device connectivity. :param gate_set: Set of supported gate types. :param n_cl_reg: number of classical registers supported. :param supports_fast_feedforward: Flag for hardware support of fast feedforward. @@ -172,7 +172,7 @@ class BackendInfo: device_name: Optional[str] version: str # hardware constraints - architecture: Union[Architecture, FullyConnected] + architecture: Optional[Union[Architecture, FullyConnected]] gate_set: Set[OpType] n_cl_reg: Optional[int] = None # additional feature support @@ -194,17 +194,21 @@ class BackendInfo: @property def nodes(self) -> List[Node]: """ - List of device nodes of the backend. + List of device nodes of the backend. Returns empty list + if the `architecture` field is not provided. :return: List of nodes. :rtype: List[Node] """ + if self.architecture is None: + return [] return self.architecture.nodes @property def n_nodes(self) -> int: """ - Number of nodes in the architecture of the device. + Number of nodes in the architecture of the device. Returns 0 + if the `architecture` field is not provided. :return: Number of nodes. :rtype: int @@ -246,7 +250,8 @@ def to_dict(self) -> Dict[str, Any]: """ self_dict = asdict(self) - self_dict["architecture"] = self_dict["architecture"].to_dict() + if self_dict["architecture"] is not None: + self_dict["architecture"] = self_dict["architecture"].to_dict() self_dict["gate_set"] = [op.value for op in self_dict["gate_set"]] self_dict["all_node_gate_errors"] = _serialize_all_node_gate_errors( self_dict["all_node_gate_errors"] @@ -279,10 +284,11 @@ def from_dict(cls, d: Dict[str, Any]) -> "BackendInfo": """ args = dict(**d) arch = args["architecture"] - if "links" in arch: - args["architecture"] = Architecture.from_dict(args["architecture"]) - else: - args["architecture"] = FullyConnected.from_dict(args["architecture"]) + if arch is not None: + if "links" in arch: + args["architecture"] = Architecture.from_dict(args["architecture"]) + else: + args["architecture"] = FullyConnected.from_dict(args["architecture"]) args["gate_set"] = {OpType(op) for op in args["gate_set"]} args["all_node_gate_errors"] = _deserialize_all_node_gate_errors( args["all_node_gate_errors"] diff --git a/pytket/tests/strategies.py b/pytket/tests/strategies.py index 8b49f32164..83d9ee288e 100644 --- a/pytket/tests/strategies.py +++ b/pytket/tests/strategies.py @@ -166,7 +166,7 @@ def backendinfo( device_name = draw(st.text(min_size=1, max_size=30)) version = draw(st.text(min_size=1, max_size=5)) # hardware constraints - arc = draw(architecture()) + arc = draw(st.one_of(st.none(), architecture())) gate_set = draw(st.sets(optypes())) supports_fast_feedforward = draw(st.booleans()) supports_reset = draw(st.booleans()) From 42ffaa74d82bcacec28b8e701fa5e28a2b18e509 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Fri, 1 Dec 2023 17:17:38 +0000 Subject: [PATCH 15/35] update pytket-qujax link (#1157) --- pytket/docs/extensions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytket/docs/extensions.rst b/pytket/docs/extensions.rst index 4975510211..465fe53f80 100644 --- a/pytket/docs/extensions.rst +++ b/pytket/docs/extensions.rst @@ -145,7 +145,7 @@ Other pytket-quantinuum pytket-cutensornet pytket-qulacs - pytket-qujax + pytket-qujax pytket-stim From 3da1a8b42606f0559c4f80199f1c9fec47f14c73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 08:36:43 +0000 Subject: [PATCH 16/35] Bump cachix/install-nix-action from 23 to 24 (#1158) Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 23 to 24. - [Release notes](https://github.com/cachix/install-nix-action/releases) - [Commits](https://github.com/cachix/install-nix-action/compare/v23...v24) --- updated-dependencies: - dependency-name: cachix/install-nix-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-with-nix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-with-nix.yml b/.github/workflows/build-with-nix.yml index e9f8c7999a..fa1722b176 100644 --- a/.github/workflows/build-with-nix.yml +++ b/.github/workflows/build-with-nix.yml @@ -15,6 +15,6 @@ jobs: runs-on: ${{matrix.os}} steps: - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v23 + - uses: cachix/install-nix-action@v24 - name: Build and test tket run: nix flake check -L From 31425f39d663f97e2bac483bb66100104fb408f8 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Mon, 4 Dec 2023 11:53:51 +0000 Subject: [PATCH 17/35] [doc] Update changelog (re. making BackendInfo.architecture optional). (#1159) --- pytket/docs/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 2923bf2b8c..54d605a629 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -4,6 +4,10 @@ Changelog Unreleased ---------- +API changes: + +* Make the ``architecture`` field in ``BackendInfo`` optional. + Deprecations: * Deprecate ``SynthesiseHQS`` pass. From 0fdb4789f7b0b8fea51b3d080afe58108edca064 Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Tue, 5 Dec 2023 10:21:37 +0000 Subject: [PATCH 18/35] Fix Cycle handling in FrameRandomisation (#1147) * Update test_FrameRandomisation.cpp * Update CMakeLists.txt * Fix Frame Randomisation * bump * Update CMakeLists.txt * Update Cycles.cpp * Update changelog.rst * Update Cycles.cpp * Update test_FrameRandomisation.cpp * working cycle fidner and insertion * Remove comments and couts * add back tests, fix formatting * clean up intersection code * Address comments --- pytket/conanfile.py | 2 +- pytket/docs/changelog.rst | 4 + tket/conanfile.py | 2 +- tket/include/tket/Characterisation/Cycles.hpp | 2 +- tket/src/Characterisation/Cycles.cpp | 133 +++++++++++++----- .../Characterisation/FrameRandomisation.cpp | 20 ++- tket/test/src/test_FrameRandomisation.cpp | 77 +++++++++- 7 files changed, 200 insertions(+), 40 deletions(-) diff --git a/pytket/conanfile.py b/pytket/conanfile.py index fb043f58f0..a1a35e6951 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.70@tket/stable") + self.requires("tket/1.2.71@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") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 54d605a629..9e25f3a684 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -11,6 +11,10 @@ API changes: Deprecations: * Deprecate ``SynthesiseHQS`` pass. + +Fixes: + +* Fix `PauliFrameRandomisation.sample_circuits`. Fixes: diff --git a/tket/conanfile.py b/tket/conanfile.py index 6346140ec8..1ac75476e3 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.70" + version = "1.2.71" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/include/tket/Characterisation/Cycles.hpp b/tket/include/tket/Characterisation/Cycles.hpp index 160fe10354..8286b98466 100644 --- a/tket/include/tket/Characterisation/Cycles.hpp +++ b/tket/include/tket/Characterisation/Cycles.hpp @@ -109,7 +109,7 @@ class CycleFinder { std::map cycle_out_edges; // Stores data structures for tracking created Cycles and associated UnitID's - CycleHistory bt; + CycleHistory cycle_history; // Given a new CutFrontier object, creates new cycles from interior vertices // and merges them with previous cycles where possible diff --git a/tket/src/Characterisation/Cycles.cpp b/tket/src/Characterisation/Cycles.cpp index 6371f992e1..2d16e3669f 100644 --- a/tket/src/Characterisation/Cycles.cpp +++ b/tket/src/Characterisation/Cycles.cpp @@ -79,49 +79,117 @@ void CycleFinder::erase_keys( for (const unsigned& key : all_erased) erase_from.erase(key); } -// Adds a new cycle to bt.key_to_cycle +// Adds a new cycle to this->cycle_history.key_to_cycle std::pair> CycleFinder::make_cycle( const Vertex& v, const EdgeVec& out_edges, const CutFrontier& cut) { // Make a new boundary, add it to "all_boundaries", update "boundary_key" map std::vector new_cycle_boundary; - std::vector new_boundary_uids; + std::set new_boundary_uids; std::set old_boundary_keys; - std::set banned_keys; + std::set not_mergeable_keys; + std::set not_mergeable_uids; // Get UnitID, add to uids for new boundary, keep note of all old boundary // keys for merging set new boundary key, add new edges to new boundary update // cycle out edges if the edge can't be found + for (const Edge& e : out_edges) { UnitID uid = unitid_from_unit_frontier(cut.u_frontier, e); - new_boundary_uids.push_back(uid); - old_boundary_keys.insert(bt.uid_to_key[uid]); - + new_boundary_uids.insert(uid); + old_boundary_keys.insert(this->cycle_history.uid_to_key[uid]); // boundary key associated with given edge e not included // i.e. e's associated in edge isn't a cycle out edge // i.e. some other non-cycle gate has been passed + + // if the edge going into the vertex is not in a previous cycle + // then it can't be merged with that cycle if (cycle_out_edges.find(circ.get_last_edge(v, e)) == cycle_out_edges.end()) { - banned_keys.insert(bt.uid_to_key[uid]); + not_mergeable_keys.insert(this->cycle_history.uid_to_key[uid]); } - bt.uid_to_key[uid] = bt.key; + this->cycle_history.uid_to_key[uid] = this->cycle_history.key; new_cycle_boundary.push_back({circ.get_last_edge(v, e), e}); update_cycle_out_edges(uid, e); } + for (const unsigned& key : old_boundary_keys) { + for (const UnitID& uid : this->cycle_history.history[key]) { + if (not_mergeable_uids.find(uid) != not_mergeable_uids.end()) { + not_mergeable_keys.insert(key); + break; + } + } + } + std::vector op_indices(out_edges.size()); std::iota(op_indices.begin(), op_indices.end(), 0); // Edges in port ordering Cycle new_cycle( new_cycle_boundary, {{circ.get_OpType_from_Vertex(v), op_indices, v}}); - bt.key_to_cycle[bt.key] = {new_cycle}; + this->cycle_history.key_to_cycle[this->cycle_history.key] = {new_cycle}; - bt.history.push_back(new_boundary_uids); - for (const unsigned& key : banned_keys) { + this->cycle_history.history.push_back( + std::vector(new_boundary_uids.begin(), new_boundary_uids.end())); + for (const unsigned& key : not_mergeable_keys) { erase_keys(key, old_boundary_keys); } + + not_mergeable_keys.clear(); + + // For each candidate cycle "cycle" given as an unsigned key + // For all UnitID "uid" in "v" that are not in "cycle" + // For all cycles between cycle_history.uid_to_key[uid] and "cycle" + // If a cycle contains both "uid" and any "uid" from "cycle" + // Set the key as not to be merged + for (const unsigned& candidate_cycle_key : old_boundary_keys) { + std::vector candidate_cycle_uid = + this->cycle_history.history[candidate_cycle_key]; + std::set not_present_uids; + for (const UnitID& candidate_uids : new_boundary_uids) { + if (std::find( + candidate_cycle_uid.begin(), candidate_cycle_uid.end(), + candidate_uids) == candidate_cycle_uid.end()) { + not_present_uids.insert(candidate_uids); + } + } + // not_present_uids now contains UnitID in the new cycle that are not in the + // cycle represented by "candidate_cycle_key" + + // We now iterate through all cycles between these UnitIDs' most recent + // cycles and our target cycle + + // If any of these cycles have both a non-present uid and any uid in the + // target cycle then this key is not mergeable and would lead to a "cycle in + // the DAG" (different type of cycle) + for (const UnitID& not_present_uid : not_present_uids) { + for (unsigned i = candidate_cycle_key; + i < this->cycle_history.uid_to_key[not_present_uid]; i++) { + std::vector history_uids = this->cycle_history.history[i]; + if (std::find( + history_uids.begin(), history_uids.end(), not_present_uid) != + history_uids.end()) { + std::vector intersection; + std::vector candidates = + cycle_history.history[candidate_cycle_key]; + std::set_intersection( + candidates.begin(), candidates.end(), history_uids.begin(), + history_uids.end(), std::back_inserter(intersection)); + if (!intersection.empty()) { + not_mergeable_keys.insert(candidate_cycle_key); + break; + } + } + } + } + } + + for (const unsigned& key : not_mergeable_keys) { + erase_keys(key, old_boundary_keys); + } + std::pair> return_keys = { - bt.key, old_boundary_keys}; - bt.key++; + this->cycle_history.key, old_boundary_keys}; + this->cycle_history.key++; return return_keys; } @@ -141,8 +209,8 @@ void CycleFinder::order_keys( // blocked. remove smaller key from keys from set as not a candidate for // merging into for (; jt != old_keys.end(); ++jt) - for (const UnitID& u0 : bt.history[*it]) - for (const UnitID& u1 : bt.history[*jt]) + for (const UnitID& u0 : this->cycle_history.history[*it]) + for (const UnitID& u1 : this->cycle_history.history[*jt]) if (u0 == u1) { bad_keys.insert(*it); goto new_loop; @@ -209,14 +277,15 @@ void CycleFinder::merge_cycles(unsigned new_key, std::set& old_keys) { std::advance(it, 1); for (; it != old_keys.end(); ++it) { unsigned merge_key = *it; - bt.key_to_cycle[base_key].merge(bt.key_to_cycle[merge_key]); - bt.key_to_cycle.erase(merge_key); - // update bt.history for "base_key" - for (const UnitID& u0 : bt.history[merge_key]) { - bt.uid_to_key[u0] = base_key; - for (const UnitID& u1 : bt.history[base_key]) + this->cycle_history.key_to_cycle[base_key].merge( + this->cycle_history.key_to_cycle[merge_key]); + this->cycle_history.key_to_cycle.erase(merge_key); + // update this->cycle_history.history for "base_key" + for (const UnitID& u0 : this->cycle_history.history[merge_key]) { + this->cycle_history.uid_to_key[u0] = base_key; + for (const UnitID& u1 : this->cycle_history.history[base_key]) if (u0 == u1) break; - bt.history[base_key].push_back(u0); + this->cycle_history.history[base_key].push_back(u0); } } } @@ -227,7 +296,6 @@ void CycleFinder::extend_cycles(const CutFrontier& cut) { // If any in edge to new cycle matches an out edge to a previous cycle, // attempt to merge cycles together for (const Vertex& v : *cut.slice) { - EdgeVec in_edges = circ.get_in_edges_of_type(v, EdgeType::Quantum); EdgeVec out_edges = circ.get_out_edges_of_type(v, EdgeType::Quantum); // Compare EdgeType::Quantum "in_edges" of vertex in slice to collection of // out edges from "active" boundaries If an "in_edge" is not equivalent to @@ -247,7 +315,7 @@ std::vector CycleFinder::get_cycles() { }; Circuit::SliceIterator slice_iter(circ, skip_func); - bt.key = 0; + this->cycle_history.key = 0; // initialization if (!(*slice_iter).empty()) { @@ -256,25 +324,24 @@ std::vector CycleFinder::get_cycles() { Edge in_edge = pair.second; if (circ.get_edgetype(in_edge) != EdgeType::Classical) { Vertex in_vert = circ.source(pair.second); - // if vertex is type from types, then vertex is in slice and need // in_edge. else can ignore. if (cycle_types_.find(circ.get_OpType_from_Vertex(in_vert)) != cycle_types_.end()) { in_edge = circ.get_last_edge(in_vert, pair.second); } - cycle_out_edges.insert({in_edge, pair.first}); - bt.uid_to_key.insert({pair.first, bt.key}); + this->cycle_history.uid_to_key.insert( + {pair.first, this->cycle_history.key}); Cycle new_cycle({{in_edge, in_edge}}, {{}}); - bt.key_to_cycle[bt.key] = new_cycle; - bt.history.push_back({pair.first}); - bt.key++; + this->cycle_history.key_to_cycle[this->cycle_history.key] = new_cycle; + this->cycle_history.history.push_back({pair.first}); + this->cycle_history.key++; } } // extend cycles automatically merges cycles that can be merge due to // overlapping multi-qubit gates - extend_cycles(slice_iter.cut_); + this->extend_cycles(slice_iter.cut_); cycle_out_edges = {}; for (const std::pair& pair : slice_iter.cut_.u_frontier->get()) { @@ -285,14 +352,14 @@ std::vector CycleFinder::get_cycles() { slice_iter.cut_ = circ.next_cut( slice_iter.cut_.u_frontier, slice_iter.cut_.b_frontier, skip_func); if (!(*slice_iter).empty()) { - extend_cycles(slice_iter.cut_); + this->extend_cycles(slice_iter.cut_); } } // Skim Cycle type from CycleHistory.key_to_cycle // Discard any Cycles with only Input Gates std::vector output_cycles; - for (std::pair entry : bt.key_to_cycle) { + for (std::pair entry : this->cycle_history.key_to_cycle) { if (entry.second.coms_.size() == 0) { throw CycleError(std::string("Cycle with no internal gates.")); } diff --git a/tket/src/Characterisation/FrameRandomisation.cpp b/tket/src/Characterisation/FrameRandomisation.cpp index c6667ce0be..eb899a9c16 100644 --- a/tket/src/Characterisation/FrameRandomisation.cpp +++ b/tket/src/Characterisation/FrameRandomisation.cpp @@ -79,8 +79,17 @@ void add_noop_frames(std::vector& cycles, Circuit& circ) { in_edge = (*it).second; } + // boundary in edges can be equal to other boundary out edges + // rewiring a vertex into these in edges replace the edge + // first see if its been rewired and use new edge if necessary + Edge out_edge = boundary.second; + it = replacement_rewiring_edges.find(out_edge); + if (it != replacement_rewiring_edges.end()) { + out_edge = (*it).second; + } + circ.rewire(input_noop_vert, {in_edge}, {EdgeType::Quantum}); - circ.rewire(output_noop_vert, {boundary.second}, {EdgeType::Quantum}); + circ.rewire(output_noop_vert, {out_edge}, {EdgeType::Quantum}); // Can guarantee both have one output edge only as just rewired Edge input_noop_out_edge = @@ -91,8 +100,12 @@ void add_noop_frames(std::vector& cycles, Circuit& circ) { barrier_ins.push_back(input_noop_out_edge); barrier_outs.push_back(output_noop_in_edge); - replacement_rewiring_edges[boundary.second] = - circ.get_out_edges_of_type(output_noop_vert, EdgeType::Quantum)[0]; + replacement_rewiring_edges.insert( + {out_edge, + circ.get_out_edges_of_type(output_noop_vert, EdgeType::Quantum)[0]}); + replacement_rewiring_edges.insert( + {in_edge, + circ.get_in_edges_of_type(input_noop_vert, EdgeType::Quantum)[0]}); full_cycle.add_vertex_pair({input_noop_vert, output_noop_vert}); } std::vector sig(barrier_ins.size(), EdgeType::Quantum); @@ -102,6 +115,7 @@ void add_noop_frames(std::vector& cycles, Circuit& circ) { circ.rewire(input_barrier_vert, barrier_ins, sig); circ.rewire(output_barrier_vert, barrier_outs, sig); } + std::vector commands = circ.get_commands(); } std::vector> get_all_frame_permutations( diff --git a/tket/test/src/test_FrameRandomisation.cpp b/tket/test/src/test_FrameRandomisation.cpp index 2829619b78..1f05b1ae96 100644 --- a/tket/test/src/test_FrameRandomisation.cpp +++ b/tket/test/src/test_FrameRandomisation.cpp @@ -16,6 +16,8 @@ #include "testutil.hpp" #include "tket/Characterisation/FrameRandomisation.hpp" +#include "tket/Predicates/CompilationUnit.hpp" +#include "tket/Predicates/PassLibrary.hpp" #include "tket/Transformations/Rebase.hpp" #include "tket/Transformations/Transform.hpp" @@ -358,5 +360,78 @@ SCENARIO( } } +SCENARIO("Frame Randomisation Segmentation Fault") { + // https://github.com/CQCL/tket/issues/1015 + PauliFrameRandomisation pfr; + Circuit c(4); + c.add_op(OpType::CX, {0, 2}); + c.add_op(OpType::Z, {0}); + c.add_op(OpType::CX, {0, 3}); + c.add_op(OpType::Z, {0}); + c.add_op(OpType::CX, {0, 3}); + c.add_op(OpType::CX, {0, 1}); + std::vector circs = pfr.sample_randomisation_circuits(c, 1); + CHECK(circs.size() == 1); +} + +SCENARIO("Frame Randomisation non-real DAG") { + // https://github.com/CQCL/tket/issues/1015 + GIVEN("CnX gate with 3 controls") { + PauliFrameRandomisation pfr; + Circuit c(4); + c.add_op(OpType::CnX, {0, 1, 2, 3}); + CompilationUnit cu(c); + RebaseTket()->apply(cu); + std::vector circs = + pfr.sample_randomisation_circuits(cu.get_circ_ref(), 1); + CHECK(circs.size() == 1); + CHECK(circs[0].get_commands().size() == 107); + } + GIVEN("CnX gate with 4 controls") { + PauliFrameRandomisation pfr; + Circuit c(5); + c.add_op(OpType::CnX, {0, 1, 2, 3, 4}); + CompilationUnit cu(c); + RebaseTket()->apply(cu); + std::vector circs = + pfr.sample_randomisation_circuits(cu.get_circ_ref(), 1); + CHECK(circs.size() == 1); + CHECK(circs[0].get_commands().size() == 293); + } + GIVEN("CnX gate with 5 controls") { + PauliFrameRandomisation pfr; + Circuit c(6); + c.add_op(OpType::CnX, {0, 1, 2, 3, 4, 5}); + CompilationUnit cu(c); + RebaseTket()->apply(cu); + std::vector circs = + pfr.sample_randomisation_circuits(cu.get_circ_ref(), 1); + CHECK(circs.size() == 1); + CHECK(circs[0].get_commands().size() == 742); + } + GIVEN("CnX gate with 6 controls") { + PauliFrameRandomisation pfr; + Circuit c(7); + c.add_op(OpType::CnX, {0, 1, 2, 3, 4, 5, 6}); + CompilationUnit cu(c); + RebaseTket()->apply(cu); + std::vector circs = + pfr.sample_randomisation_circuits(cu.get_circ_ref(), 1); + CHECK(circs.size() == 1); + CHECK(circs[0].get_commands().size() == 1118); + } + GIVEN("CnX gate with 7 controls") { + PauliFrameRandomisation pfr; + Circuit c(8); + c.add_op(OpType::CnX, {0, 1, 2, 3, 4, 5, 6, 7}); + CompilationUnit cu(c); + RebaseTket()->apply(cu); + std::vector circs = + pfr.sample_randomisation_circuits(cu.get_circ_ref(), 1); + CHECK(circs.size() == 1); + CHECK(circs[0].get_commands().size() == 1570); + } +} + } // namespace test_FrameRandomisation -} // namespace tket +} // namespace tket \ No newline at end of file From 7fdb4f6643b94873a374f8d044d1bad164defbe7 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Tue, 5 Dec 2023 15:25:24 +0000 Subject: [PATCH 19/35] [doc] Miscellaneous doc fixes and improvements (#1163) --- pytket/binders/circuit/Circuit/main.cpp | 16 ++++++++++---- pytket/binders/passes.cpp | 28 +++++++++++++++++++------ pytket/docs/circuit.rst | 3 +++ pytket/pytket/_tket/circuit.pyi | 4 ++++ pytket/pytket/_tket/passes.pyi | 16 ++++++++------ pytket/pytket/passes/auto_rebase.py | 4 ++++ 6 files changed, 55 insertions(+), 16 deletions(-) diff --git a/pytket/binders/circuit/Circuit/main.cpp b/pytket/binders/circuit/Circuit/main.cpp index 44ad29ea8c..89d46d3d05 100644 --- a/pytket/binders/circuit/Circuit/main.cpp +++ b/pytket/binders/circuit/Circuit/main.cpp @@ -271,8 +271,12 @@ void def_circuit(py::class_> &pyCircuit) { } return b_regs; }, - "Get all classical registers.\n\n:return: List of " - ":py:class:`BitRegister`") + "Get all classical registers.\n\n" + "This property is only valid if the bits in the circuit are " + "organized into registers (i.e. all bit indices are single numbers " + "and all sets of bits with the same string identifier consist of " + "bits indexed consecutively from zero).\n\n" + ":return: List of :py:class:`BitRegister`") .def( "get_q_register", [](Circuit &circ, const std::string &name) { @@ -307,8 +311,12 @@ void def_circuit(py::class_> &pyCircuit) { } return q_regs; }, - "Get all quantum registers.\n\n:return: List of " - ":py:class:`QubitRegister`") + "Get all quantum registers.\n\n" + "This property is only valid if the qubits in the circuit are " + "organized into registers (i.e. all qubit indices are single numbers " + "and all sets of qubits with the same string identifier consist of " + "qubits indexed consecutively from zero).\n\n" + ":return: List of :py:class:`QubitRegister`") .def( "add_qubit", &Circuit::add_qubit, "Constructs a single qubit with the given id.\n\n:param id: " diff --git a/pytket/binders/passes.cpp b/pytket/binders/passes.cpp index 1228d0ec7b..0d6129cf54 100644 --- a/pytket/binders/passes.cpp +++ b/pytket/binders/passes.cpp @@ -373,6 +373,7 @@ PYBIND11_MODULE(passes, m) { "Using the `allow_swaps=True` (default) option, qubits will be " "swapped when convenient to further reduce the two-qubit gate count " "(only applicable when decomposing to CX gates).\n\n" + "Note that gates containing symbolic parameters are not squashed.\n\n" ":param target_2qb_gate: OpType to decompose to. Either TK2 or CX.\n" ":param cx_fidelity: Estimated CX gate fidelity, used when " "target_2qb_gate=CX.\n" @@ -406,7 +407,8 @@ PYBIND11_MODULE(passes, m) { "over ZZMax and CX if the decomposition results in fewer two-qubit " "gates.\n\n" "All TK2 gate parameters must be normalised, i.e. they must satisfy " - "`NormalisedTK2Predicate`.\n\n" + "`NormalisedTK2Predicate`. (This can be achieved by applying the " + ":py:meth:`NormaliseTK2` pass beforehand.)\n\n" "Using the `allow_swaps=True` (default) option, qubits will be swapped " "when convenient to reduce the two-qubit gate count of the decomposed " "TK2.\n\n" @@ -433,6 +435,9 @@ PYBIND11_MODULE(passes, m) { "ThreeQubitSquash", &ThreeQubitSquash, "Squash three-qubit subcircuits into subcircuits having fewer CX gates, " "when possible, and apply Clifford simplification." + "\n\nThe circuit to which this is applied must consist of single-qubit, " + "pure-classical and CX gates, and Measure, Collapse, Reset, Phase and " + "conditional gates." "\n\n:param allow_swaps: whether to allow implicit wire swaps", py::arg("allow_swaps") = true); m.def( @@ -496,7 +501,11 @@ PYBIND11_MODULE(passes, m) { "2-qubit gate (which may be CX or TK2) and TK1 gates." "\n\n:param allow_swaps: whether to allow implicit wire swaps", py::arg("allow_swaps") = true, py::arg("target_2qb_gate") = OpType::CX); - m.def("RebaseTket", &RebaseTket, "Converts all gates to CX, TK1 and Phase."); + m.def( + "RebaseTket", &RebaseTket, + "Converts all gates to CX, TK1 and Phase. " + "(Any Measure, Reset and Collapse operations are left untouched; " + "Conditional gates are also allowed.)"); m.def( "RemoveRedundancies", &RemoveRedundancies, "Removes gate-inverse pairs, merges rotations, removes identity " @@ -606,7 +615,10 @@ PYBIND11_MODULE(passes, m) { "4. applies the `tk1_replacement` function to each of these triples " ":math:`(a,b,c)` to generate replacement circuits." "\n\n" - ":param gateset: the allowed operations in the rebased circuit" + ":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)" "\n:param cx_replacement: the equivalent circuit to replace a CX gate " "using two qubit gates from the desired basis (can use any single qubit " "OpTypes)" @@ -614,7 +626,8 @@ PYBIND11_MODULE(passes, m) { "Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the desired " "basis" "\n:return: a pass that rebases to the given gate set (possibly " - "including conditional and phase operations)", + "including conditional and phase operations, and Measure, Reset and " + "Collapse)", py::arg("gateset"), py::arg("cx_replacement"), py::arg("tk1_replacement")); @@ -631,7 +644,10 @@ PYBIND11_MODULE(passes, m) { "4. if TK2 is not in `gateset`. applies the `tk1_replacement` function " "to each TK1(a,b,c)." "\n\n" - ":param gateset: the allowed operations in the rebased circuit\n" + ":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)\n" ":param tk2_replacement: a function which, given the parameters (a,b,c) " "of an XXPhase(a)YYPhase(b)ZZPhase(c) triple, returns an equivalent " "circuit in the desired basis\n" @@ -639,7 +655,7 @@ PYBIND11_MODULE(passes, m) { "of an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the " "desired basis\n" ":return: a pass that rebases to the given gate set (possibly including " - "conditional and phase operations)", + "conditional and phase operations, and Measure, Reset and Collapse)", py::arg("gateset"), py::arg("tk2_replacement"), py::arg("tk1_replacement")); diff --git a/pytket/docs/circuit.rst b/pytket/docs/circuit.rst index b8f4c3342c..4cae8e2a11 100644 --- a/pytket/docs/circuit.rst +++ b/pytket/docs/circuit.rst @@ -38,6 +38,9 @@ pytket.circuit .. autoclass:: pytket._tket.circuit.PauliExpBox :special-members: :members: +.. autoclass:: pytket._tket.circuit.PauliExpPairBox + :special-members: + :members: .. autoclass:: pytket._tket.circuit.ToffoliBox :special-members: :members: diff --git a/pytket/pytket/_tket/circuit.pyi b/pytket/pytket/_tket/circuit.pyi index 76282ef6d5..52f1283406 100644 --- a/pytket/pytket/_tket/circuit.pyi +++ b/pytket/pytket/_tket/circuit.pyi @@ -2339,6 +2339,8 @@ class Circuit: """ Get all classical registers. + This property is only valid if the bits in the circuit are organized into registers (i.e. all bit indices are single numbers and all sets of bits with the same string identifier consist of bits indexed consecutively from zero). + :return: List of :py:class:`BitRegister` """ @property @@ -2392,6 +2394,8 @@ class Circuit: """ Get all quantum registers. + This property is only valid if the qubits in the circuit are organized into registers (i.e. all qubit indices are single numbers and all sets of qubits with the same string identifier consist of qubits indexed consecutively from zero). + :return: List of :py:class:`QubitRegister` """ @property diff --git a/pytket/pytket/_tket/passes.pyi b/pytket/pytket/_tket/passes.pyi index a45c9cbd82..c4bf9d2b7d 100644 --- a/pytket/pytket/_tket/passes.pyi +++ b/pytket/pytket/_tket/passes.pyi @@ -318,7 +318,7 @@ def DecomposeTK2(allow_swaps: bool = True, **kwargs: Any) -> BasePass: If no fidelities are provided, the TK2 gates will be decomposed exactly using CX gates. For equal fidelities, ZZPhase will be prefered over ZZMax and CX if the decomposition results in fewer two-qubit gates. - All TK2 gate parameters must be normalised, i.e. they must satisfy `NormalisedTK2Predicate`. + All TK2 gate parameters must be normalised, i.e. they must satisfy `NormalisedTK2Predicate`. (This can be achieved by applying the :py:meth:`NormaliseTK2` pass beforehand.) Using the `allow_swaps=True` (default) option, qubits will be swapped when convenient to reduce the two-qubit gate count of the decomposed TK2. @@ -410,6 +410,8 @@ def KAKDecomposition(target_2qb_gate: pytket._tket.circuit.OpType = pytket._tket Using the `allow_swaps=True` (default) option, qubits will be swapped when convenient to further reduce the two-qubit gate count (only applicable when decomposing to CX gates). + Note that gates containing symbolic parameters are not squashed. + :param target_2qb_gate: OpType to decompose to. Either TK2 or CX. :param cx_fidelity: Estimated CX gate fidelity, used when target_2qb_gate=CX. :param allow_swaps: Whether to allow implicit wire swaps. @@ -488,10 +490,10 @@ def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], cx_replacement: pytk 3. converts any single-qubit gates not in the gate type set to the form :math:`\mathrm{Rz}(a)\mathrm{Rx}(b)\mathrm{Rz}(c)` (in matrix-multiplication order, i.e. reverse order in the circuit); 4. applies the `tk1_replacement` function to each of these triples :math:`(a,b,c)` to generate replacement circuits. - :param gateset: the allowed operations in the rebased circuit + :param gateset: the allowed operations in the rebased circuit (in addition, Measure, Reset and Collapse operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) :param cx_replacement: the equivalent circuit to replace a CX gate using two qubit gates from the desired basis (can use any single qubit OpTypes) :param tk1_replacement: a function which, given the parameters of an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the desired basis - :return: a pass that rebases to the given gate set (possibly including conditional and phase operations) + :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: @@ -503,14 +505,14 @@ def RebaseCustom(gateset: set[pytket._tket.circuit.OpType], tk2_replacement: typ 3. converts any single-qubit gates not in the gate type set to TK1; 4. if TK2 is not in `gateset`. applies the `tk1_replacement` function to each TK1(a,b,c). - :param gateset: the allowed operations in the rebased circuit + :param gateset: the allowed operations in the rebased circuit (in addition, Measure, Reset and Collapse operations are always allowed and are left alone; conditional operations may be present; and Phase gates may also be introduced by the rebase) :param tk2_replacement: a function which, given the parameters (a,b,c) of an XXPhase(a)YYPhase(b)ZZPhase(c) triple, returns an equivalent circuit in the desired basis :param tk1_replacement: a function which, given the parameters (a,b,c) of an Rz(a)Rx(b)Rz(c) triple, returns an equivalent circuit in the desired basis - :return: a pass that rebases to the given gate set (possibly including conditional and phase operations) + :return: a pass that rebases to the given gate set (possibly including conditional and phase operations, and Measure, Reset and Collapse) """ def RebaseTket() -> BasePass: """ - Converts all gates to CX, TK1 and Phase. + Converts all gates to CX, TK1 and Phase. (Any Measure, Reset and Collapse operations are left untouched; Conditional gates are also allowed.) """ def RemoveBarriers() -> BasePass: """ @@ -605,6 +607,8 @@ def ThreeQubitSquash(allow_swaps: bool = True) -> BasePass: """ Squash three-qubit subcircuits into subcircuits having fewer CX gates, when possible, and apply Clifford simplification. + The circuit to which this is applied must consist of single-qubit, pure-classical and CX gates, and Measure, Collapse, Reset, Phase and conditional gates. + :param allow_swaps: whether to allow implicit wire swaps """ def ZXGraphlikeOptimisation() -> BasePass: diff --git a/pytket/pytket/passes/auto_rebase.py b/pytket/pytket/passes/auto_rebase.py index 6af5c690c6..2c395c44b0 100644 --- a/pytket/pytket/passes/auto_rebase.py +++ b/pytket/pytket/passes/auto_rebase.py @@ -134,6 +134,10 @@ def auto_rebase_pass(gateset: Set[OpType], allow_swaps: bool = False) -> BasePas Raises an error if no known decompositions can be found, in which case try using RebaseCustom with your own decompositions. + In addition to the gate types in ``gateset``, any ``Measure``, ``Reset`` and + ``Collapse`` operations in the original circuit are retained. Conditional + operations are also allowed. ``Phase`` gates may also be introduced. + :param gateset: Set of supported OpTypes, target gate set. :type gateset: FrozenSet[OpType] :raises NoAutoRebase: No suitable decomposition found. From 3f67a56b14a8b274aa8fd58667b07232135c247c Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:18:11 +0000 Subject: [PATCH 20/35] [infra] Add PR template. (#1164) --- .github/pull_request_template.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..558b03336f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +# Description + +Please summarise the changes. + +# Related issues + +Please mention any github issues addressed by this PR. + +# Checklist + +- [ ] I have performed a self-review of my code. +- [ ] I have commented hard-to-understand parts of my code. +- [ ] I have made corresponding changes to the public API documentation. +- [ ] I have added tests that prove my fix is effective or that my feature works. +- [ ] I have updated the changelog with any user-facing changes. From ef9fbbb9b463fa49384567f1f0206197e3ab392f Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Fri, 8 Dec 2023 17:06:30 +0000 Subject: [PATCH 21/35] Update Noise Aware Placement for Circuits with only single qubit gates (#1165) * Add catch for empty pattern graph * Update test_NoiseAwarePlacement.cpp * bump * Add further comments * Update changelog.rst * Update pytket/docs/changelog.rst Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --------- Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- pytket/conanfile.py | 2 +- pytket/docs/changelog.rst | 7 ++-- tket/conanfile.py | 2 +- tket/src/Placement/NoiseAwarePlacement.cpp | 29 ++++++++++++++ .../Placement/test_NoiseAwarePlacement.cpp | 40 +++++++++++++++---- 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/pytket/conanfile.py b/pytket/conanfile.py index a1a35e6951..892bd3e024 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.71@tket/stable") + self.requires("tket/1.2.72@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") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 9e25f3a684..ec96c6642e 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -14,12 +14,11 @@ Deprecations: Fixes: -* Fix `PauliFrameRandomisation.sample_circuits`. - -Fixes: - * Ensure that squashing long sequences of gates via unitary multiplication does not produce non-unitary results due to rounding errors. +* Fix `PauliFrameRandomisation.sample_circuits`. +* For `Circuit` with no 2-qubit gates, `NoiseAwarePlacement` now assigns `Qubit` to `Node` in `Architecture` + with lowest reported error rates. 1.22.0 (November 2023) diff --git a/tket/conanfile.py b/tket/conanfile.py index 1ac75476e3..5f5599e424 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.71" + version = "1.2.72" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/src/Placement/NoiseAwarePlacement.cpp b/tket/src/Placement/NoiseAwarePlacement.cpp index d215ebb6f4..18fffc0b01 100644 --- a/tket/src/Placement/NoiseAwarePlacement.cpp +++ b/tket/src/Placement/NoiseAwarePlacement.cpp @@ -123,6 +123,35 @@ std::vector> NoiseAwarePlacement::get_all_placement_maps( const Circuit& circ_, unsigned matches) const { std::vector weighted_pattern_edges = this->default_pattern_weighting(circ_); + + if (weighted_pattern_edges.empty()) { + // => there are no two-qubit gates in the input circuit + // in this case, as this method is "noise-aware", assign + // qubits in the circuit to the lowest-error architecture nodes + std::vector> all_node_errors; + for (const Node& node : this->architecture_.nodes()) { + all_node_errors.push_back( + {this->characterisation_.get_error(node), node}); + } + // make sure all_node_errors goes from best->worst error rates + // N.B. `get_error` returns "0", i.e. no error, if the `Node` passed + // is not held in the characterisation + std::sort( + all_node_errors.begin(), all_node_errors.end(), + [](const auto& lhs, const auto& rhs) { + // Compare based on the float values in descending order + return lhs.first < rhs.first; + }); + std::vector circ_qubits = circ_.all_qubits(); + TKET_ASSERT(all_node_errors.size() >= circ_qubits.size()); + std::map placement_map; + // assign circuit qubits to Node with best error rate + for (std::size_t i = 0; i < circ_qubits.size(); i++) { + placement_map.insert({circ_qubits[i], all_node_errors[i].second}); + } + return {placement_map}; + } + std::vector> placement_maps = this->get_all_weighted_subgraph_monomorphisms( circ_, weighted_pattern_edges, true); diff --git a/tket/test/src/Placement/test_NoiseAwarePlacement.cpp b/tket/test/src/Placement/test_NoiseAwarePlacement.cpp index 213ea7d822..534bd70020 100644 --- a/tket/test/src/Placement/test_NoiseAwarePlacement.cpp +++ b/tket/test/src/Placement/test_NoiseAwarePlacement.cpp @@ -41,7 +41,7 @@ SCENARIO("Base NoiseAwarePlacement class") { Circuit circuit(1); NoiseAwarePlacement placement(architecture); placement.place(circuit); - REQUIRE(circuit.all_qubits()[0] == Qubit(Placement::unplaced_reg(), 0)); + REQUIRE(circuit.all_qubits()[0] == Node(0)); } GIVEN( "Two qubit unconnected circuit, two qubit Architecture, " @@ -51,8 +51,8 @@ SCENARIO("Base NoiseAwarePlacement class") { Circuit circuit(2); NoiseAwarePlacement placement(architecture); placement.place(circuit); - REQUIRE(circuit.all_qubits()[0] == Qubit(Placement::unplaced_reg(), 0)); - REQUIRE(circuit.all_qubits()[1] == Qubit(Placement::unplaced_reg(), 1)); + REQUIRE(circuit.all_qubits()[0] == Node(0)); + REQUIRE(circuit.all_qubits()[1] == Node(1)); } GIVEN( "Three qubit unconnected circuit, two qubit Architecture, " @@ -231,8 +231,8 @@ SCENARIO("Base NoiseAwarePlacement class") { circuit.add_op(OpType::CX, {1, 2}); circuit.add_op(OpType::CX, {2, 3}); circuit.add_op(OpType::CX, {0, 3}); - // In this case there are many valid placements, it happens to return this - // one + // In this case there are many valid placements, it happens to return + // this one NoiseAwarePlacement placement(architecture); std::map map = placement.get_placement_map(circuit); REQUIRE(map[Qubit(0)] == Node(3)); @@ -276,8 +276,8 @@ SCENARIO("Base NoiseAwarePlacement class") { DeviceCharacterisation characterisation_link_node( op_node_errors, op_link_errors); - // Here the difference in single qubit error rates makes this placement (or - // a rotation of) best + // Here the difference in single qubit error rates makes this placement + // (or a rotation of) best placement.set_characterisation(characterisation_link_node); map = placement.get_placement_map(circuit); @@ -408,5 +408,31 @@ SCENARIO("Base NoiseAwarePlacement class") { REQUIRE(map[Qubit(4)] == Node(1)); REQUIRE(map[Qubit(5)] == Node(2)); } + GIVEN( + "A circuit with only single-qubit gates, assigns Qubits to Nodes with " + "lowest single qubit error rates.") { + std::vector> edges = { + {0, 1}, {1, 2}, {0, 2}, {2, 3}}; + Architecture architecture(edges); + Circuit circuit(3); + circuit.add_op(OpType::H, {0}); + circuit.add_op(OpType::H, {1}); + circuit.add_op(OpType::H, {2}); + + avg_node_errors_t op_node_errors; + op_node_errors[Node(0)] = 0.25; + op_node_errors[Node(1)] = 0.01; + op_node_errors[Node(2)] = 0.01; + op_node_errors[Node(3)] = 0.05; + + DeviceCharacterisation characterisation(op_node_errors, {}, {}); + NoiseAwarePlacement placement(architecture); + placement.set_characterisation(characterisation); + + std::map placement_map = placement.get_placement_map(circuit); + REQUIRE(placement_map[Qubit(0)] == Node(1)); + REQUIRE(placement_map[Qubit(1)] == Node(2)); + REQUIRE(placement_map[Qubit(2)] == Node(3)); + } } } // namespace tket \ No newline at end of file From 0382636f340779fb932606e6387ec93aa4509bcc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 11:10:57 +0000 Subject: [PATCH 22/35] Bump actions/setup-python from 4 to 5 (#1166) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-without-conan.yml | 2 +- .github/workflows/build_and_test.yml | 22 +++++++++++----------- .github/workflows/build_libs.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/packages.yml | 2 +- .github/workflows/release.yml | 16 ++++++++-------- .github/workflows/test_libs.yml | 2 +- .github/workflows/test_libs_all.yml | 2 +- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build-without-conan.yml b/.github/workflows/build-without-conan.yml index faa7aa4264..740f00987b 100644 --- a/.github/workflows/build-without-conan.yml +++ b/.github/workflows/build-without-conan.yml @@ -190,7 +190,7 @@ jobs: cmake --build . -j2 cmake --install . - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install pytket diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 3cadc33dd5..a23609e420 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -30,7 +30,7 @@ jobs: tket_package_exists: ${{ steps.tket_package_exists.outputs.tket_package_exists }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' - uses: dorny/paths-filter@v2.11.1 @@ -106,7 +106,7 @@ jobs: run: sudo apt update - name: Select Python 3.10 # otherwise turtlebrowser/get-conan@v1.2 fails on macos-12 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install conan @@ -260,17 +260,17 @@ jobs: conan create recipes/pybind11_json/all --version 0.2.13 - name: Set up Python (pull request) if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.9" - name: Set up Python (push) if: github.event_name == 'push' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Set up Python (schedule) if: github.event_name == 'schedule' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" - name: Build pytket @@ -330,7 +330,7 @@ jobs: - uses: actions/checkout@v4 - name: Select Python 3.10 # otherwise turtlebrowser/get-conan@v1.2 fails on macos-12 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install conan @@ -370,12 +370,12 @@ jobs: run: brew install python@3.9 - name: Set up Python (push) if: github.event_name == 'push' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Set up Python (schedule) if: github.event_name == 'schedule' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" - name: Build pytket @@ -484,17 +484,17 @@ jobs: conan create recipes/pybind11_json/all --version 0.2.13 - name: Set up Python (3.9) if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.9" - name: Set up Python (3.10) if: github.event_name == 'push' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Set up Python (3.11) if: github.event_name == 'schedule' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" - name: Build pytket diff --git a/.github/workflows/build_libs.yml b/.github/workflows/build_libs.yml index 23153790a3..0430bfd22c 100644 --- a/.github/workflows/build_libs.yml +++ b/.github/workflows/build_libs.yml @@ -64,7 +64,7 @@ jobs: [IO.File]::WriteAllText($f, $normalized_file) } - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install conan diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 314034598f..ae68a0fd58 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python 3.x - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install black and pylint diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index f2d5cf4061..55f1c56cbe 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -25,7 +25,7 @@ jobs: - name: Select Python 3.10 # otherwise turtlebrowser/get-conan@v1.2 fails on macos-12 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ef92819e26..c3de51c791 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,7 +83,7 @@ jobs: fetch-depth: '0' - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install conan @@ -126,7 +126,7 @@ jobs: - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Set up Python ${{ matrix.python-version }} if: matrix.python-version != '3.9' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Set up Python 3.9 @@ -184,7 +184,7 @@ jobs: - name: Build tket run: conan create tket --user tket --channel stable --build=missing -o boost/*:header_only=True -o tklog/*:shared=True -o tket/*:shared=True -tf `"`" - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Build wheel @@ -208,7 +208,7 @@ jobs: python3-version: ['9', '10', '11'] steps: - name: Set up Python 3.${{ matrix.python3-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.${{ matrix.python3-version }}" - name: Download wheels @@ -273,7 +273,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Download wheels @@ -303,7 +303,7 @@ jobs: steps: - name: Set up Python ${{ matrix.python-version }} if: matrix.python-version != '3.9' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Set up Python 3.9 @@ -335,7 +335,7 @@ jobs: python-version: ['3.9', '3.10', '3.11'] steps: - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Download wheel @@ -385,7 +385,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' - name: Download wheels diff --git a/.github/workflows/test_libs.yml b/.github/workflows/test_libs.yml index 80e7907877..b5ec34c9bc 100644 --- a/.github/workflows/test_libs.yml +++ b/.github/workflows/test_libs.yml @@ -69,7 +69,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install conan diff --git a/.github/workflows/test_libs_all.yml b/.github/workflows/test_libs_all.yml index 5063fa2a23..6f95f00703 100644 --- a/.github/workflows/test_libs_all.yml +++ b/.github/workflows/test_libs_all.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python 3.10 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install conan From 996de981a057da47e409c0b64058d1001f1497b8 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Mon, 11 Dec 2023 15:51:13 +0000 Subject: [PATCH 23/35] Bump tket version. (#1171) --- pytket/conanfile.py | 2 +- tket/conanfile.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 892bd3e024..62830cf6d0 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.72@tket/stable") + self.requires("tket/1.2.73@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") diff --git a/tket/conanfile.py b/tket/conanfile.py index 5f5599e424..248d2ee0ea 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.72" + version = "1.2.73" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" From c14aabe9b004af4a8837b7445bd6e6cf21b6f5d0 Mon Sep 17 00:00:00 2001 From: CalMacCQ <93673602+CalMacCQ@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:31:03 +0000 Subject: [PATCH 24/35] change random-greedy to greedy (#1168) Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- pytket/pytket/zx/tensor_eval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytket/pytket/zx/tensor_eval.py b/pytket/pytket/zx/tensor_eval.py index 4449a5281b..a63bdbdbd1 100644 --- a/pytket/pytket/zx/tensor_eval.py +++ b/pytket/pytket/zx/tensor_eval.py @@ -199,7 +199,7 @@ def _tensor_from_basic_diagram(diag: ZXDiagram) -> np.ndarray: tensor_list.append(qt) net = qtn.TensorNetwork(tensor_list) net.full_simplify_(seq="ADCR") - res_ten = net.contract(output_inds=res_indices, optimize="random-greedy") + res_ten = net.contract(output_inds=res_indices, optimize="greedy") result: np.ndarray if type(res_ten) == qtn.Tensor: result = res_ten.data From 248c1eca294525edaa2a4bc799e2dbe8cf43fc3c Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Thu, 14 Dec 2023 09:22:14 +0000 Subject: [PATCH 25/35] [infra] Bump symengine and gmp versions (#1176) --- .github/workflows/build-without-conan.yml | 6 +++--- build-without-conan.md | 14 +++++++------- pytket/conanfile.py | 6 +++--- tket/conanfile.py | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-without-conan.yml b/.github/workflows/build-without-conan.yml index 740f00987b..aff43ee7fe 100644 --- a/.github/workflows/build-without-conan.yml +++ b/.github/workflows/build-without-conan.yml @@ -40,9 +40,9 @@ jobs: - name: Install symengine run: | cd ${TMP_DIR} - wget https://github.com/symengine/symengine/releases/download/v0.11.1/symengine-0.11.1.tar.gz - tar xzvf symengine-0.11.1.tar.gz - cd symengine-0.11.1/ + wget https://github.com/symengine/symengine/releases/download/v0.11.2/symengine-0.11.2.tar.gz + tar xzvf symengine-0.11.2.tar.gz + cd symengine-0.11.2/ mkdir build cd build cmake -GNinja -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DBUILD_TESTS=OFF -DBUILD_BENCHMARKS=OFF .. diff --git a/build-without-conan.md b/build-without-conan.md index 7e2dc7128f..d66ae6b8ed 100644 --- a/build-without-conan.md +++ b/build-without-conan.md @@ -34,10 +34,10 @@ cd boost_1_83_0/ ``` cd ${TMP_DIR} -wget https://gmplib.org/download/gmp/gmp-6.2.1.tar.bz2 -bzip2 -dk gmp-6.2.1.tar.bz2 -tar xvf gmp-6.2.1.tar -cd gmp-6.2.1/ +wget https://gmplib.org/download/gmp/gmp-6.3.0.tar.bz2 +bzip2 -dk gmp-6.3.0.tar.bz2 +tar xvf gmp-6.3.0.tar +cd gmp-6.3.0/ ./configure --prefix=${INSTALL_DIR} --enable-cxx=yes make make check @@ -48,9 +48,9 @@ make install ``` cd ${TMP_DIR} -wget https://github.com/symengine/symengine/releases/download/v0.11.1/symengine-0.11.1.tar.gz -tar xzvf symengine-0.11.1.tar.gz -cd symengine-0.11.1/ +wget https://github.com/symengine/symengine/releases/download/v0.11.2/symengine-0.11.2.tar.gz +tar xzvf symengine-0.11.2.tar.gz +cd symengine-0.11.2/ mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DBUILD_TESTS=OFF -DBUILD_BENCHMARKS=OFF .. diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 62830cf6d0..6f231d3fd5 100644 --- a/pytket/conanfile.py +++ b/pytket/conanfile.py @@ -32,14 +32,14 @@ def package(self): cmake.install() def requirements(self): - self.requires("tket/1.2.73@tket/stable") + self.requires("tket/1.2.74@tket/stable") self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") self.requires("tkassert/0.3.4@tket/stable") self.requires("tkwsm/0.3.6@tket/stable") self.requires("tktokenswap/0.3.6@tket/stable") - self.requires("symengine/0.11.1") - self.requires("gmp/6.2.1") + self.requires("symengine/0.11.2") + self.requires("gmp/6.3.0") self.requires("pybind11/2.11.1") self.requires("nlohmann_json/3.11.3") self.requires("pybind11_json/0.2.13") diff --git a/tket/conanfile.py b/tket/conanfile.py index 248d2ee0ea..d9f9823e8b 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.73" + version = "1.2.74" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" @@ -112,7 +112,7 @@ def requirements(self): # libraries installed from remote: # https://quantinuumsw.jfrog.io/artifactory/api/conan/tket1-libs self.requires("boost/1.83.0", transitive_headers=True) - self.requires("symengine/0.11.1", transitive_headers=True) + self.requires("symengine/0.11.2", transitive_headers=True) self.requires("eigen/3.4.0", transitive_headers=True) self.requires("nlohmann_json/3.11.3", transitive_headers=True) self.requires("tklog/0.3.3@tket/stable") From 7f04263b3cbce77327c62043db6191b88cd516d8 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Thu, 14 Dec 2023 11:58:11 +0000 Subject: [PATCH 26/35] [infra] Update to catch2 3.5.0 (#1177) --- .github/workflows/build-without-conan.yml | 6 +++--- build-without-conan.md | 6 +++--- libs/tkassert/test/conanfile.py | 2 +- libs/tklog/test/conanfile.py | 2 +- libs/tkrng/test/conanfile.py | 2 +- libs/tktokenswap/test/conanfile.py | 2 +- libs/tkwsm/test/conanfile.py | 2 +- pytket/conanfile.py | 2 +- tket/conanfile.py | 4 ++-- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-without-conan.yml b/.github/workflows/build-without-conan.yml index aff43ee7fe..6508c654ac 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.3.2.tar.gz - tar xzvf v3.3.2.tar.gz - cd Catch2-3.3.2/ + wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.5.0.tar.gz + tar xzvf v3.5.0.tar.gz + cd Catch2-3.5.0/ mkdir build cd build cmake -GNinja -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} .. diff --git a/build-without-conan.md b/build-without-conan.md index d66ae6b8ed..affe4fdfd7 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.3.2.tar.gz -tar xzvf v3.3.2.tar.gz -cd Catch2-3.3.2/ +wget https://github.com/catchorg/Catch2/archive/refs/tags/v3.5.0.tar.gz +tar xzvf v3.5.0.tar.gz +cd Catch2-3.5.0/ mkdir build cd build cmake -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} .. diff --git a/libs/tkassert/test/conanfile.py b/libs/tkassert/test/conanfile.py index 395d89f11c..d5aa278b16 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.3.2") + self.requires("catch2/3.5.0") diff --git a/libs/tklog/test/conanfile.py b/libs/tklog/test/conanfile.py index 68f90cefc2..414798317e 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.3.2") + self.requires("catch2/3.5.0") diff --git a/libs/tkrng/test/conanfile.py b/libs/tkrng/test/conanfile.py index c490692260..606348783e 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.3.2") + self.requires("catch2/3.5.0") diff --git a/libs/tktokenswap/test/conanfile.py b/libs/tktokenswap/test/conanfile.py index 18c548eb1c..98f8d9c651 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.6") self.requires("tkrng/0.3.3@tket/stable") - self.requires("catch2/3.3.2") + self.requires("catch2/3.5.0") diff --git a/libs/tkwsm/test/conanfile.py b/libs/tkwsm/test/conanfile.py index 5802b184bc..b51828fc2c 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.6") self.requires("tkassert/0.3.4@tket/stable") self.requires("tkrng/0.3.3@tket/stable") - self.requires("catch2/3.3.2") + self.requires("catch2/3.5.0") diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 6f231d3fd5..1c115d411b 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.74@tket/stable") + self.requires("tket/1.2.75@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") diff --git a/tket/conanfile.py b/tket/conanfile.py index d9f9823e8b..86a2dbf956 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.74" + version = "1.2.75" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" @@ -121,7 +121,7 @@ def requirements(self): self.requires("tktokenswap/0.3.6@tket/stable") self.requires("tkwsm/0.3.6@tket/stable") if self.build_test(): - self.test_requires("catch2/3.3.2") + self.test_requires("catch2/3.5.0") if self.build_proptest(): self.test_requires("rapidcheck/cci.20220514") From 3b83482813e290ec88a2b5513c83cfa470f62d64 Mon Sep 17 00:00:00 2001 From: yao-cqc <75305462+yao-cqc@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:06:46 +0000 Subject: [PATCH 27/35] Fix invalid registers returned by ``Circuit.q_registers`` and ``Circuit.c_registers``. (#1185) --- pytket/binders/circuit/Circuit/main.cpp | 77 +++++++++++++------------ pytket/docs/changelog.rst | 1 + pytket/tests/circuit_test.py | 19 ++++++ 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/pytket/binders/circuit/Circuit/main.cpp b/pytket/binders/circuit/Circuit/main.cpp index 89d46d3d05..17a3a98e15 100644 --- a/pytket/binders/circuit/Circuit/main.cpp +++ b/pytket/binders/circuit/Circuit/main.cpp @@ -58,6 +58,43 @@ UnitID to_cpp_unitid(const PyUnitID &py_unitid) { return get(py_unitid); } +template +std::vector get_unit_registers(Circuit &circ) { + static_assert( + std::is_same::value || + std::is_same::value, + "T must be either QubitRegister or BitRegister"); + using T2 = typename std::conditional< + std::is_same::value, Qubit, Bit>::type; + std::vector unitids; + if constexpr (std::is_same::value) { + unitids = circ.all_qubits(); + } else if constexpr (std::is_same::value) { + unitids = circ.all_bits(); + } + // map from register name to unsigned indices + std::map> unit_map; + std::vector regs; + for (const T2 &unitid : unitids) { + // UnitRegisters only describe registers with 1-d indices + if (unitid.reg_dim() > 1) continue; + auto it = unit_map.find(unitid.reg_name()); + if (it == unit_map.end()) { + unit_map.insert({unitid.reg_name(), {unitid.index()[0]}}); + } else { + it->second.insert(unitid.index()[0]); + } + } + regs.reserve(unit_map.size()); + for (auto const &it : unit_map) { + // only return registers that are indexed consecutively from zero + if (*it.second.rbegin() == it.second.size() - 1) { + regs.emplace_back(it.first, it.second.size()); + } + } + return regs; +} + void init_circuit_add_op(py::class_> &c); void init_circuit_add_classical_op( py::class_> &c); @@ -252,25 +289,7 @@ void def_circuit(py::class_> &pyCircuit) { ":py:class:`BitRegister`", py::arg("name")) .def_property_readonly( - "c_registers", - [](Circuit &circ) { - bit_vector_t all_bits = circ.all_bits(); - std::map bits_map; - std::vector b_regs; - for (const Bit &bit : all_bits) { - auto it = bits_map.find(bit.reg_name()); - if (it == bits_map.end()) { - bits_map.insert({bit.reg_name(), 1}); - } else { - it->second++; - } - } - b_regs.reserve(bits_map.size()); - for (auto const &it : bits_map) { - b_regs.emplace_back(it.first, it.second); - } - return b_regs; - }, + "c_registers", &get_unit_registers, "Get all classical registers.\n\n" "This property is only valid if the bits in the circuit are " "organized into registers (i.e. all bit indices are single numbers " @@ -292,25 +311,7 @@ void def_circuit(py::class_> &pyCircuit) { ":py:class:`QubitRegister`", py::arg("name")) .def_property_readonly( - "q_registers", - [](Circuit &circ) { - qubit_vector_t all_qbs = circ.all_qubits(); - std::map qbs_map; - std::vector q_regs; - for (const Qubit &qb : all_qbs) { - auto it = qbs_map.find(qb.reg_name()); - if (it == qbs_map.end()) { - qbs_map.insert({qb.reg_name(), 1}); - } else { - it->second++; - } - } - q_regs.reserve(qbs_map.size()); - for (auto const &it : qbs_map) { - q_regs.emplace_back(it.first, it.second); - } - return q_regs; - }, + "q_registers", &get_unit_registers, "Get all quantum registers.\n\n" "This property is only valid if the qubits in the circuit are " "organized into registers (i.e. all qubit indices are single numbers " diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index ec96c6642e..2a862f860a 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -19,6 +19,7 @@ Fixes: * Fix `PauliFrameRandomisation.sample_circuits`. * For `Circuit` with no 2-qubit gates, `NoiseAwarePlacement` now assigns `Qubit` to `Node` in `Architecture` with lowest reported error rates. +* Fix invalid registers returned by ``Circuit.q_registers`` and ``Circuit.c_registers``. 1.22.0 (November 2023) diff --git a/pytket/tests/circuit_test.py b/pytket/tests/circuit_test.py index 243619aece..ea4b860e79 100644 --- a/pytket/tests/circuit_test.py +++ b/pytket/tests/circuit_test.py @@ -1150,6 +1150,25 @@ def test_getting_registers() -> None: assert q_regs[1] == QubitRegister("test_qr", 10) +def test_getting_registers_with_non_consective_indices() -> None: + # https://github.com/CQCL/tket/issues/1160 + c = Circuit() + c.add_qubit(Qubit(3)) + c.add_qubit(Qubit(2)) + c.add_bit(Bit(3)) + c.add_qubit(Qubit("a", 0)) + c.add_qubit(Qubit("a", 1)) + c.add_qubit(Qubit("a", 2)) + c.add_bit(Bit("b", 0)) + c.add_bit(Bit("b", 1)) + c_regs = c.c_registers + assert len(c_regs) == 1 + assert c_regs[0] == BitRegister("b", 2) + q_regs = c.q_registers + assert len(q_regs) == 1 + assert q_regs[0] == QubitRegister("a", 3) + + def test_measuring_registers() -> None: c = Circuit() with pytest.raises(RuntimeError) as e: From a031a2f78f1be9825004c3fa1792a65564fce86f Mon Sep 17 00:00:00 2001 From: Seyon Sivarajah Date: Wed, 20 Dec 2023 10:58:45 +0000 Subject: [PATCH 28/35] [feature] add a Circuit method for Reset (#1184) * [feature] add a Circuit method for Reset Commonly used enough that having to switch to add_gate confuses people. * try upping tket lib version * update version in conanfile * format * regen stubs * regen stubs --------- Co-authored-by: Melf --- pytket/binders/circuit/Circuit/add_op.cpp | 20 ++++++++++++++++++++ pytket/conanfile.py | 2 +- pytket/pytket/_tket/circuit.pyi | 14 ++++++++++++++ pytket/tests/add_circuit_test.py | 10 ++++++---- tket/conanfile.py | 2 +- 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/pytket/binders/circuit/Circuit/add_op.cpp b/pytket/binders/circuit/Circuit/add_op.cpp index 087c84afde..5c3bfe9afb 100644 --- a/pytket/binders/circuit/Circuit/add_op.cpp +++ b/pytket/binders/circuit/Circuit/add_op.cpp @@ -1067,6 +1067,16 @@ void init_circuit_add_op(py::class_> &c) { "(Z) basis." "\n\n:return: the new :py:class:`Circuit`", py::arg("qubit"), py::arg("bit_index")) + .def( + "Reset", + [](Circuit *circ, unsigned qb, const py::kwargs &kwargs) { + return add_gate_method_noparams( + circ, OpType::Reset, {qb}, kwargs); + }, + "Appends a Reset operation. Sets a qubit to the Z-basis 0 state. " + "Non-unitary operation." + "\n\n:return: the new :py:class:`Circuit`", + py::arg("qubit")) .def( "Rz", [](Circuit *circ, const Expr &angle, unsigned qb, @@ -1680,6 +1690,16 @@ void init_circuit_add_op(py::class_> &c) { "(Z) basis." "\n\n:return: the new :py:class:`Circuit`", py::arg("qubit"), py::arg("bit")) + .def( + "Reset", + [](Circuit *circ, const Qubit &qb, const py::kwargs &kwargs) { + return add_gate_method_noparams( + circ, OpType::Reset, {qb}, kwargs); + }, + "Appends a Reset operation. Sets a qubit to the Z-basis 0 state. " + "Non-unitary operation." + "\n\n:return: the new :py:class:`Circuit`", + py::arg("qubit")) .def( "Rz", [](Circuit *circ, const Expr &angle, const Qubit &qb, diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 1c115d411b..18802883b5 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.75@tket/stable") + self.requires("tket/1.2.77@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") diff --git a/pytket/pytket/_tket/circuit.pyi b/pytket/pytket/_tket/circuit.pyi index 52f1283406..788b155fde 100644 --- a/pytket/pytket/_tket/circuit.pyi +++ b/pytket/pytket/_tket/circuit.pyi @@ -515,6 +515,20 @@ class Circuit: :return: the new :py:class:`Circuit` """ @typing.overload + def Reset(self, qubit: int, **kwargs: Any) -> Circuit: + """ + Appends a Reset operation. Sets a qubit to the Z-basis 0 state. Non-unitary operation. + + :return: the new :py:class:`Circuit` + """ + @typing.overload + def Reset(self, qubit: pytket._tket.unit_id.Qubit, **kwargs: Any) -> Circuit: + """ + Appends a Reset operation. Sets a qubit to the Z-basis 0 state. Non-unitary operation. + + :return: the new :py:class:`Circuit` + """ + @typing.overload def Rx(self, angle: sympy.Expr | float, qubit: int, **kwargs: Any) -> Circuit: """ Appends an Rx gate with a possibly symbolic angle (specified in half-turns). diff --git a/pytket/tests/add_circuit_test.py b/pytket/tests/add_circuit_test.py index 536afb84c9..3f01ce0810 100644 --- a/pytket/tests/add_circuit_test.py +++ b/pytket/tests/add_circuit_test.py @@ -16,21 +16,23 @@ import pytest -def gen_bell_state() -> Circuit: +def gen_bell_state(reset_start: bool = False) -> Circuit: circ = Circuit(2) + if reset_start: + circ = circ.Reset(0).Reset(1) circ.H(0) circ.CX(0, 1) return circ def test_direct_add() -> None: - a = gen_bell_state() + a = gen_bell_state(True) b = Circuit(1) b.add_gate(OpType.X, [0]) a.add_circuit(b, [0]) - assert a.n_gates == 3 + assert a.n_gates == 5 assert a.n_qubits == 2 - assert a.depth() == 3 + assert a.depth() == 4 def test_swap_add() -> None: diff --git a/tket/conanfile.py b/tket/conanfile.py index 86a2dbf956..ff253dab81 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.75" + version = "1.2.77" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" From 4e62011109ca5669ecebb335abbbf9efa1e6367a Mon Sep 17 00:00:00 2001 From: David Yonge-Mallo Date: Thu, 21 Dec 2023 12:13:36 +0100 Subject: [PATCH 29/35] Add Visual Studio to .gitignore. (#1181) Co-authored-by: cqc-melf <70640934+cqc-melf@users.noreply.github.com> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 69e53f7f91..48491ab516 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ pytket/tests/qasm_test_files/testout6.qasm # IDEs .idea .vscode +.vs #nix result From a18287dbaf9f9af6d562524fedf9d79d6b36e1c6 Mon Sep 17 00:00:00 2001 From: cqc-melf <70640934+cqc-melf@users.noreply.github.com> Date: Tue, 2 Jan 2024 09:17:25 +0000 Subject: [PATCH 30/35] update github actions (#1186) --- .github/workflows/build_and_test.yml | 8 ++++---- .github/workflows/coverage.yml | 8 ++++---- .github/workflows/docs.yml | 4 ++-- .github/workflows/release.yml | 28 ++++++++++++++-------------- .github/workflows/test_libs.yml | 4 ++-- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index a23609e420..bdd522acf2 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -302,13 +302,13 @@ jobs: ./.github/workflows/build-docs - name: Upload artefact if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pytket_docs path: pytket/docs/build/html/ - name: Upload pytket coverage artefact if: github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pytket_test_coverage path: pytket/tests/htmlcov @@ -532,7 +532,7 @@ jobs: with: ref: gh-pages - name: Download artefact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: pytket_test_coverage path: htmlcov/ @@ -558,7 +558,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Download artefact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: pytket_test_coverage path: pytket-test-coverage/ diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index cbf1b262dd..ec4d2bc279 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -82,7 +82,7 @@ jobs: gcovr --print-summary --html --html-details -r ./tket --exclude-lines-by-pattern '.*\bTKET_ASSERT\(.*\);' --object-directory ${PWD}/build/tket/build/Debug/CMakeFiles/tket.dir/src -o test-coverage/index.html --decisions > test-coverage/summary.txt cat test-coverage/summary.txt - name: Upload artefact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test_coverage path: test-coverage/ @@ -95,7 +95,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Download artefact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: test_coverage path: test-coverage/ @@ -121,7 +121,7 @@ jobs: with: ref: gh-pages - name: Download artefact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: test_coverage path: test-coverage/ @@ -150,7 +150,7 @@ jobs: with: ref: gh-pages - name: Download artefact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: test_coverage path: test-coverage/ diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c567f072a8..d661c5743d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,7 +17,7 @@ jobs: - name: Build Doxygen docs run: cd tket && doxygen - name: Upload artefact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: tket_docs path: tket/doc/html/ @@ -30,7 +30,7 @@ jobs: with: ref: gh-pages - name: Download artefact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: tket_docs path: api/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3de51c791..df3add5a54 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ jobs: docker exec -e PY_TAG="cp3${{ matrix.python3-version }}-cp3${{ matrix.python3-version }}" -e CONAN_PROFILE=linux-x86_64-gcc10-libstdc++ linux_build /bin/bash -c "/tket/.github/workflows/linuxbuildwheel" mkdir wheelhouse docker cp linux_build:/tket/pytket/audited/. wheelhouse/ - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Linux_3.${{ matrix.python3-version }}_wheel path: wheelhouse/ @@ -63,7 +63,7 @@ jobs: run: | export DOCKER_HOST=unix://${HOME}/.docker/run/docker.sock docker rm --force -v linux_build - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Linux_aarch64_3.${{ matrix.python3-version }}_wheel path: wheelhouse/ @@ -108,7 +108,7 @@ jobs: pip install -U pip build delocate python -m build delocate-wheel -v -w "$GITHUB_WORKSPACE/wheelhouse/" "dist/pytket-"*".whl" - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: MacOS_${{ matrix.python-version }}_wheel path: wheelhouse/ @@ -154,7 +154,7 @@ jobs: python${{ matrix.python-version }} -m pip install -U pip build delocate python${{ matrix.python-version }} -m build delocate-wheel -v -w "$GITHUB_WORKSPACE/wheelhouse/" "dist/pytket-"*".whl" - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: MacOS_M1_${{ matrix.python-version }}_wheel path: wheelhouse/ @@ -194,7 +194,7 @@ jobs: cd pytket python -m pip install -U pip build python -m build --outdir "${{ github.workspace }}/wheelhouse" - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Windows_${{ matrix.python-version }}_wheel path: wheelhouse/ @@ -212,7 +212,7 @@ jobs: with: python-version: "3.${{ matrix.python3-version }}" - name: Download wheels - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: Linux_3.${{ matrix.python3-version }}_wheel path: wheelhouse/ @@ -240,7 +240,7 @@ jobs: with: path: tket - name: Download wheel - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: Linux_aarch64_3.${{ matrix.python3-version }}_wheel path: wheelhouse/ @@ -277,7 +277,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Download wheels - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: MacOS_${{ matrix.python-version }}_wheel path: wheelhouse/ @@ -310,7 +310,7 @@ jobs: if: matrix.python-version == '3.9' run: brew install python@3.9 - name: Download wheels - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: MacOS_M1_${{ matrix.python-version }}_wheel path: wheelhouse/ @@ -339,7 +339,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Download wheel - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: Windows_${{ matrix.python-version }}_wheel path: wheelhouse/ @@ -363,7 +363,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Download all wheels - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: path: wheelhouse - name: Put them all in the dist folder @@ -389,7 +389,7 @@ jobs: with: python-version: '3.10' - name: Download wheels - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: Linux_3.10_wheel path: wheelhouse/ @@ -403,7 +403,7 @@ jobs: timeout-minutes: 20 run: ./.github/workflows/build-docs - name: Upload artefact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: pytket_docs path: pytket/docs/build/html/ @@ -419,7 +419,7 @@ jobs: with: ref: gh-pages - name: Download artefact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: pytket_docs path: api/ diff --git a/.github/workflows/test_libs.yml b/.github/workflows/test_libs.yml index b5ec34c9bc..d97d6c96d5 100644 --- a/.github/workflows/test_libs.yml +++ b/.github/workflows/test_libs.yml @@ -131,7 +131,7 @@ jobs: gcovr --print-summary --html --html-details -r ${GITHUB_WORKSPACE}/libs/${{ matrix.lib }} --exclude-lines-by-pattern '.*\bTKET_ASSERT\(.*\);' --object-directory ${GITHUB_WORKSPACE}/build/${{ matrix.lib }}/build/Debug/CMakeFiles/${{ matrix.lib }}.dir/src -o ${{ matrix.lib }}-coverage/index.html --decisions > ${{ matrix.lib }}-coverage/summary.txt cat ${{ matrix.lib }}-coverage/summary.txt - name: upload artefact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.lib }}_coverage path: ${{ matrix.lib }}-coverage/ @@ -157,7 +157,7 @@ jobs: with: ref: gh-pages - name: download artefact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: ${{ matrix.lib }}_coverage path: ${{ matrix.lib }}-coverage/ From 7031d4c9ff0c4501d1ab42ac05348df568508d4c Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Wed, 3 Jan 2024 12:30:19 +0000 Subject: [PATCH 31/35] [infra] Fix determination of ccache path on Windows (#1192) --- .github/workflows/build_and_test.yml | 14 ++++++++------ pytket/conanfile.py | 2 +- tket/conanfile.py | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index bdd522acf2..758449dff2 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -459,17 +459,19 @@ jobs: key: tket-dynamic-visual-studio-windows-2022-${{ steps.current_time.outputs.formattedTime }} restore-keys: | tket-dynamic-visual-studio-windows-2022 - - name: get ccache version - id: ccache-ver + - name: get ccache path + id: ccache-path shell: bash run: | - ccache_ver=$(choco list -e ccache | grep "ccache" | grep -ioE '[0-9]+\.[0-9]+\.[0-9]+') - echo "Found ccache version ${ccache_ver}" - echo "ccache_ver=${ccache_ver}" >> $GITHUB_OUTPUT + ccache --shimgen-noop > ccache-info.txt || true + a=$(cat ccache-info.txt | grep executable) + # strip off the initial " path to executable: " + b=${a:22} + echo "ccache_path=${b}" >> $GITHUB_OUTPUT - name: Build tket if: needs.check_changes.outputs.tket_changed == 'true' run: | - $env:TKET_VSGEN_CCACHE_EXE = 'C:\\ProgramData\\chocolatey\\lib\\ccache\\tools\\ccache-${{ steps.ccache-ver.outputs.ccache_ver }}-windows-x86_64\\ccache.exe' + $env:TKET_VSGEN_CCACHE_EXE = '${{ steps.ccache-path.outputs.ccache_path }}' conan build tket --user tket --channel stable -o boost/*:header_only=True -o tklog/*:shared=True -o tket/*:shared=True conan export-pkg tket --user tket --channel stable -o boost/*:header_only=True -o tklog/*:shared=True -o tket/*:shared=True -tf `"`" - name: Install tket diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 18802883b5..b8274039a6 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.77@tket/stable") + self.requires("tket/1.2.78@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") diff --git a/tket/conanfile.py b/tket/conanfile.py index ff253dab81..4201e7f9f9 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.77" + version = "1.2.78" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" From e1ddb5fc1a09eada06b5582964c759812f374478 Mon Sep 17 00:00:00 2001 From: Alec Edgington Date: Fri, 5 Jan 2024 13:48:24 +0000 Subject: [PATCH 32/35] Update version and changelog. --- pytket/docs/changelog.rst | 4 ++-- pytket/docs/conf.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 2a862f860a..5966e369fe 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -1,8 +1,8 @@ Changelog ========= -Unreleased ----------- +1.23.0 (January 2024) +--------------------- API changes: diff --git a/pytket/docs/conf.py b/pytket/docs/conf.py index 5dbbd9b684..80ca904040 100644 --- a/pytket/docs/conf.py +++ b/pytket/docs/conf.py @@ -38,9 +38,9 @@ author = "Quantinuum" # The short X.Y version -version = "1.22" +version = "1.23" # The full version, including alpha/beta/rc tags -release = "1.22.0" +release = "1.23.0" # -- General configuration --------------------------------------------------- From cf1f24ad3723e51353780689ad747cf40e347767 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:29:41 +0000 Subject: [PATCH 33/35] Only check pytket coverage for PRs to develop. (#1198) --- .github/workflows/build_and_test.yml | 2 +- .github/workflows/compare-pytket-coverage | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 758449dff2..283d1705de 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -555,7 +555,7 @@ jobs: check_pytket_coverage: name: Check pytket line and branch coverage needs: build_test_pytket_ubuntu - if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + if: (github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'develop') || github.event_name == 'workflow_dispatch' runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/compare-pytket-coverage b/.github/workflows/compare-pytket-coverage index d54747fd09..8c5c14d23a 100755 --- a/.github/workflows/compare-pytket-coverage +++ b/.github/workflows/compare-pytket-coverage @@ -69,9 +69,9 @@ def compare(old_cov_file, new_cov_file): print(f"Branch coverage: {new_branch_rate}") print() - if new_line_rate < old_line_rate: + if new_line_rate + 0.01 < old_line_rate: sys.exit("Line coverage has decreased!") - if new_branch_rate < old_branch_rate: + if new_branch_rate + 0.01 < old_branch_rate: sys.exit("Branch coverage has decreased!") From a765aab211b14ba092733936ee0c10db1ccdc757 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Mon, 8 Jan 2024 16:45:05 +0000 Subject: [PATCH 34/35] Merge pull request #1201 from CQCL/bugfix/revert-refactor Revert "Unitary Synthesis of ChoiMixTableau for Diagonalisation (#941)" --- pytket/conanfile.py | 2 +- pytket/docs/changelog.rst | 2 + pytket/tests/ansatz_sequence_test.py | 6 +- tket/CMakeLists.txt | 2 + tket/conanfile.py | 2 +- tket/include/tket/Circuit/CircUtils.hpp | 26 +- tket/include/tket/Circuit/PauliExpBoxes.hpp | 42 - tket/include/tket/Clifford/ChoiMixTableau.hpp | 21 +- .../tket/Clifford/SymplecticTableau.hpp | 41 +- tket/include/tket/Clifford/UnitaryTableau.hpp | 12 - tket/include/tket/Converters/Converters.hpp | 83 +- tket/include/tket/Converters/PauliGadget.hpp | 83 ++ .../tket/Diagonalisation/Diagonalisation.hpp | 39 +- tket/src/Circuit/CircUtils.cpp | 206 ++- tket/src/Circuit/PauliExpBoxes.cpp | 92 +- tket/src/Clifford/ChoiMixTableau.cpp | 179 ++- tket/src/Clifford/SymplecticTableau.cpp | 247 ++-- tket/src/Clifford/UnitaryTableau.cpp | 212 +-- .../Converters/ChoiMixTableauConverters.cpp | 1211 ++++++++--------- tket/src/Converters/PauliGadget.cpp | 381 ++++++ tket/src/Converters/PauliGraphConverters.cpp | 1 + .../Converters/UnitaryTableauConverters.cpp | 44 +- tket/src/Diagonalisation/Diagonalisation.cpp | 406 ------ .../src/Transformations/PauliOptimisation.cpp | 8 +- tket/test/CMakeLists.txt | 1 - tket/test/src/test_ChoiMixTableau.cpp | 389 +----- tket/test/src/test_Diagonalisation.cpp | 124 -- tket/test/src/test_PauliGraph.cpp | 20 +- tket/test/src/test_PhaseGadget.cpp | 5 +- tket/test/src/test_UnitaryTableau.cpp | 134 -- 30 files changed, 1557 insertions(+), 2464 deletions(-) create mode 100644 tket/include/tket/Converters/PauliGadget.hpp create mode 100644 tket/src/Converters/PauliGadget.cpp delete mode 100644 tket/test/src/test_Diagonalisation.cpp diff --git a/pytket/conanfile.py b/pytket/conanfile.py index b8274039a6..ebd9da69b4 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.78@tket/stable") + self.requires("tket/1.2.82@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") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 5966e369fe..8db5217f06 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -20,6 +20,8 @@ Fixes: * For `Circuit` with no 2-qubit gates, `NoiseAwarePlacement` now assigns `Qubit` to `Node` in `Architecture` with lowest reported error rates. * Fix invalid registers returned by ``Circuit.q_registers`` and ``Circuit.c_registers``. +* Fix regression (introduced in 1.22.0) in compilation performance with certain + sequences of passes. 1.22.0 (November 2023) diff --git a/pytket/tests/ansatz_sequence_test.py b/pytket/tests/ansatz_sequence_test.py index 4406ace0e8..7dd1825954 100644 --- a/pytket/tests/ansatz_sequence_test.py +++ b/pytket/tests/ansatz_sequence_test.py @@ -88,9 +88,9 @@ def test_nontrivial_sequence() -> None: GraphColourMethod.Exhaustive: (3, 28, 20, 19), }, PauliPartitionStrat.NonConflictingSets: { - GraphColourMethod.LargestFirst: (6, 28, 28, 26), - GraphColourMethod.Lazy: (6, 28, 28, 28), - GraphColourMethod.Exhaustive: (6, 28, 28, 28), + GraphColourMethod.LargestFirst: (6, 28, 28, 28), + GraphColourMethod.Lazy: (6, 28, 28, 26), + GraphColourMethod.Exhaustive: (6, 28, 28, 26), }, } diff --git a/tket/CMakeLists.txt b/tket/CMakeLists.txt index 8b23409ac8..1ca87c9a7f 100644 --- a/tket/CMakeLists.txt +++ b/tket/CMakeLists.txt @@ -224,6 +224,7 @@ 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 @@ -376,6 +377,7 @@ 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/conanfile.py b/tket/conanfile.py index 4201e7f9f9..fbf3b8c8bd 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.78" + version = "1.2.82" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/include/tket/Circuit/CircUtils.hpp b/tket/include/tket/Circuit/CircUtils.hpp index 4acf5e30d4..c9706161ac 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 angle phase parameter + * @param t phase parameter * @param cx_config CX configuration * * @return phase gadget implementation wrapped in a ConjugationBox */ Circuit phase_gadget( - unsigned n_qubits, const Expr& angle, + unsigned n_qubits, const Expr& t, CXConfigType cx_config = CXConfigType::Snake); /** @@ -127,29 +127,13 @@ 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; coefficient gives rotation angle in half-turns + * @param paulis Pauli operators + * @param t angle in half-turns * @param cx_config CX configuration * @return Pauli gadget implementation wrapped in a ConjugationBox */ Circuit pauli_gadget( - SpSymPauliTensor paulis, 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, + const std::vector& paulis, const Expr& t, CXConfigType cx_config = CXConfigType::Snake); /** diff --git a/tket/include/tket/Circuit/PauliExpBoxes.hpp b/tket/include/tket/Circuit/PauliExpBoxes.hpp index 2e0180c5b9..78dc171ea3 100644 --- a/tket/include/tket/Circuit/PauliExpBoxes.hpp +++ b/tket/include/tket/Circuit/PauliExpBoxes.hpp @@ -202,46 +202,4 @@ class PauliExpCommutingSetBox : public Box { CXConfigType cx_config_; }; -/** - * 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 96c1483dca..1a3a8dae91 100644 --- a/tket/include/tket/Clifford/ChoiMixTableau.hpp +++ b/tket/include/tket/Clifford/ChoiMixTableau.hpp @@ -45,8 +45,7 @@ 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. get_row() will automatically transpose the input segment term so - * it is presented as RxS s.t. SCR = C. + * product. * * 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 @@ -94,7 +93,6 @@ 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); /** @@ -124,23 +122,13 @@ 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. - * Returns a pair of Pauli strings RxS such that SCR = C + * Read off a row as a Pauli string */ row_tensor_t get_row(unsigned i) const; /** - * Combine rows into a single row. - * Returns a pair of Pauli strings RxS such that SCR = C + * Combine rows into a single row */ row_tensor_t get_row_product(const std::vector& rows) const; @@ -154,10 +142,7 @@ 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 c6bd327fd9..86c7c63532 100644 --- a/tket/include/tket/Clifford/SymplecticTableau.hpp +++ b/tket/include/tket/Clifford/SymplecticTableau.hpp @@ -20,6 +20,12 @@ namespace tket { +// Forward declare friend classes for converters +class ChoiMixTableau; +class UnitaryTableau; +class UnitaryRevTableau; +class Circuit; + /** * Boolean encoding of Pauli * = ==> I @@ -130,13 +136,10 @@ class SymplecticTableau { void row_mult(unsigned ra, unsigned rw, Complex coeff = 1.); /** - * Applies a chosen gate to the given qubit(s) + * Applies an S/V/CX 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); @@ -170,19 +173,29 @@ 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 */ @@ -193,6 +206,18 @@ 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 25b3b3dd80..45388acda0 100644 --- a/tket/include/tket/Clifford/UnitaryTableau.hpp +++ b/tket/include/tket/Clifford/UnitaryTableau.hpp @@ -101,14 +101,8 @@ 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); @@ -242,14 +236,8 @@ 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 f629de88ba..fc0775c2e4 100644 --- a/tket/include/tket/Converters/Converters.hpp +++ b/tket/include/tket/Converters/Converters.hpp @@ -47,88 +47,13 @@ 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. - * - * 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. + * corresponding outputs in the circuit */ -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); +std::pair cm_tableau_to_circuit( + const ChoiMixTableau &circ); PauliGraph circuit_to_pauli_graph(const Circuit &circ); diff --git a/tket/include/tket/Converters/PauliGadget.hpp b/tket/include/tket/Converters/PauliGadget.hpp new file mode 100644 index 0000000000..6413ee34c5 --- /dev/null +++ b/tket/include/tket/Converters/PauliGadget.hpp @@ -0,0 +1,83 @@ +// Copyright 2019-2023 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 ce3a75198c..aa66d8f370 100644 --- a/tket/include/tket/Diagonalisation/Diagonalisation.hpp +++ b/tket/include/tket/Diagonalisation/Diagonalisation.hpp @@ -60,38 +60,13 @@ void apply_conjugations( SpSymPauliTensor &qps, const Conjugations &conjugations); /** - * 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. + * 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. */ -std::pair reduce_pauli_to_z( - const SpPauliStabiliser &pauli, CXConfigType cx_config); - -/** - * 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); +bool conjugate_with_xxphase3( + const Qubit &qb_a, const Qubit &qb_b, Conjugations &conjugations, + Circuit &cliff_circ); } // namespace tket diff --git a/tket/src/Circuit/CircUtils.cpp b/tket/src/Circuit/CircUtils.cpp index bfac83d4ec..7989494311 100644 --- a/tket/src/Circuit/CircUtils.cpp +++ b/tket/src/Circuit/CircUtils.cpp @@ -22,7 +22,6 @@ #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" @@ -270,79 +269,152 @@ std::pair decompose_2cx_DV(const Eigen::Matrix4cd &U) { } Circuit phase_gadget(unsigned n_qubits, const Expr &t, CXConfigType cx_config) { - return pauli_gadget( - SpSymPauliTensor(DensePauliMap(n_qubits, Pauli::Z), t), cx_config); -} - -Circuit pauli_gadget(SpSymPauliTensor paulis, CXConfigType cx_config) { - if (SpPauliString(paulis.string) == SpPauliString{}) { - Circuit phase_circ(paulis.size()); - phase_circ.add_phase(-paulis.coeff / 2); - return phase_circ; + // 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; + } + 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}); + } + 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}); + } + 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}); + 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}); + } + for (unsigned i = 0; i < n_qubits - dense_end; i++) + uncompute.add_op( + OpType::CX, {dense_end + i, dense_end - 1 - i}); + 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) { + unsigned i = q, j = q - 1; + // 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}); + 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]}); + } + } + break; + } } - std::pair diag = - reduce_pauli_to_z(SpPauliStabiliser(paulis.string), cx_config); - Circuit compute = diag.first; - qubit_vector_t all_qubits = compute.all_qubits(); - unit_map_t mapping = compute.flatten_registers(); - Circuit action(all_qubits.size()); - action.add_op(OpType::Rz, paulis.coeff, {mapping.at(diag.second)}); - Circuit circ(all_qubits, {}); ConjugationBox box( - std::make_shared(compute), std::make_shared(action)); - circ.add_box(box, all_qubits); - return circ; + std::make_shared(compute), std::make_shared(action), + std::make_shared(uncompute)); + new_circ.add_box(box, new_circ.all_qubits()); + return new_circ; } -Circuit pauli_gadget_pair( - SpSymPauliTensor paulis0, SpSymPauliTensor paulis1, - CXConfigType cx_config) { - if (SpPauliString(paulis0.string) == SpPauliString{}) { - Circuit p1_circ = pauli_gadget(paulis1, cx_config); - p1_circ.add_phase(-paulis0.coeff / 2); - return p1_circ; - } else if (SpPauliString(paulis1.string) == SpPauliString{}) { - Circuit p0_circ = pauli_gadget(paulis0, cx_config); - p0_circ.add_phase(-paulis1.coeff / 2); - return p0_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 (paulis0.commutes_with(paulis1)) { - std::tuple diag = reduce_commuting_paulis_to_zi_iz( - SpPauliStabiliser(paulis0.string), SpPauliStabiliser(paulis1.string), - cx_config); - Circuit &diag_circ = std::get<0>(diag); - qubit_vector_t all_qubits = diag_circ.all_qubits(); - unit_map_t mapping = diag_circ.flatten_registers(); - Circuit rot_circ(all_qubits.size()); - rot_circ.add_op( - OpType::Rz, paulis0.coeff, {mapping.at(std::get<1>(diag))}); - rot_circ.add_op( - OpType::Rz, paulis1.coeff, {mapping.at(std::get<2>(diag))}); - ConjugationBox box( - std::make_shared(diag_circ), - std::make_shared(rot_circ)); - Circuit circ(all_qubits, {}); - circ.add_box(box, all_qubits); - return circ; - } else { - std::pair diag = reduce_anticommuting_paulis_to_z_x( - SpPauliStabiliser(paulis0.string), SpPauliStabiliser(paulis1.string), - cx_config); - Circuit &diag_circ = diag.first; - qubit_vector_t all_qubits = diag_circ.all_qubits(); - unit_map_t mapping = diag_circ.flatten_registers(); - Circuit rot_circ(all_qubits.size()); - rot_circ.add_op( - OpType::Rz, paulis0.coeff, {mapping.at(diag.second)}); - rot_circ.add_op( - OpType::Rx, paulis1.coeff, {mapping.at(diag.second)}); - ConjugationBox box( - std::make_shared(diag_circ), - std::make_shared(rot_circ)); - Circuit circ(all_qubits, {}); - circ.add_box(box, all_qubits); + if (qubits.empty()) { + circ.add_phase(-t / 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()); + return circ; } void replace_CX_with_TK2(Circuit &c) { diff --git a/tket/src/Circuit/PauliExpBoxes.cpp b/tket/src/Circuit/PauliExpBoxes.cpp index 049121b571..23f81c9d8e 100644 --- a/tket/src/Circuit/PauliExpBoxes.cpp +++ b/tket/src/Circuit/PauliExpBoxes.cpp @@ -18,6 +18,7 @@ #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" @@ -60,11 +61,7 @@ Op_ptr PauliExpBox::symbol_substitution( } void PauliExpBox::generate_circuit() const { - // 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_)); + Circuit circ = pauli_gadget(paulis_.string, paulis_.coeff, cx_config_); circ_ = std::make_shared(circ); } @@ -152,12 +149,8 @@ Op_ptr PauliExpPairBox::symbol_substitution( } void PauliExpPairBox::generate_circuit() const { - // 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_)); + Circuit circ = Circuit(paulis0_.size()); + append_pauli_gadget_pair(circ, paulis0_, paulis1_, cx_config_); circ_ = std::make_shared(circ); } @@ -313,7 +306,7 @@ void PauliExpCommutingSetBox::generate_circuit() const { Circuit phase_poly_circ(n_qubits); for (const SpSymPauliTensor &pgp : gadgets) { - phase_poly_circ.append(pauli_gadget(pgp, CXConfigType::Snake)); + append_single_pauli_gadget(phase_poly_circ, pgp); } phase_poly_circ.decompose_boxes_recursively(); PhasePolyBox ppbox(phase_poly_circ); @@ -370,79 +363,4 @@ Op_ptr PauliExpCommutingSetBox::from_json(const nlohmann::json &j) { REGISTER_OPFACTORY(PauliExpCommutingSetBox, PauliExpCommutingSetBox) -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 80f0765937..b481d54893 100644 --- a/tket/src/Clifford/ChoiMixTableau.cpp +++ b/tket/src/Clifford/ChoiMixTableau.cpp @@ -103,13 +103,11 @@ 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 = @@ -117,8 +115,7 @@ 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() ^ - (n_ys % 2 == 1); + phase(r) = row.first.is_real_negative() ^ row.second.is_real_negative(); ++r; } tab_ = SymplecticTableau(xmat, zmat, phase); @@ -128,30 +125,22 @@ 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 { 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; +unsigned ChoiMixTableau::get_n_inputs() const { + unsigned n = 0; BOOST_FOREACH ( tableau_col_index_t::left_const_reference entry, col_index_.left) { - if (entry.first.second == TableauSegment::Input) - ins.push_back(entry.first.first); + if (entry.first.second == TableauSegment::Input) ++n; } - return ins; + return n; } -qubit_vector_t ChoiMixTableau::output_qubits() const { - qubit_vector_t outs; +unsigned ChoiMixTableau::get_n_outputs() const { + unsigned n = 0; BOOST_FOREACH ( tableau_col_index_t::left_const_reference entry, col_index_.left) { - if (entry.first.second == TableauSegment::Output) - outs.push_back(entry.first.first); + if (entry.first.second == TableauSegment::Output) ++n; } - return outs; + return n; } ChoiMixTableau::row_tensor_t ChoiMixTableau::stab_to_row_tensor( @@ -184,22 +173,17 @@ PauliStabiliser ChoiMixTableau::row_tensor_to_stab( } ChoiMixTableau::row_tensor_t ChoiMixTableau::get_row(unsigned i) const { - 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; + return stab_to_row_tensor(tab_.get_pauli(i)); } 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 = stab_to_row_tensor(tab_.get_pauli(i)); + row_tensor_t row_i = get_row(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; @@ -210,26 +194,11 @@ 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}); @@ -241,16 +210,20 @@ void ChoiMixTableau::apply_gate( OpType type, const qubit_vector_t& qbs, TableauSegment seg) { switch (type) { case OpType::Z: { - apply_Z(qbs.at(0), seg); + apply_S(qbs.at(0), seg); + apply_S(qbs.at(0), seg); break; } case OpType::X: { - apply_X(qbs.at(0), seg); + apply_V(qbs.at(0), seg); + apply_V(qbs.at(0), seg); break; } case OpType::Y: { - apply_Z(qbs.at(0), seg); - apply_X(qbs.at(0), seg); + apply_S(qbs.at(0), seg); + apply_S(qbs.at(0), seg); + apply_V(qbs.at(0), seg); + apply_V(qbs.at(0), seg); break; } case OpType::S: { @@ -259,7 +232,8 @@ void ChoiMixTableau::apply_gate( } case OpType::Sdg: { apply_S(qbs.at(0), seg); - apply_Z(qbs.at(0), seg); + apply_S(qbs.at(0), seg); + apply_S(qbs.at(0), seg); break; } case OpType::SX: @@ -270,11 +244,14 @@ void ChoiMixTableau::apply_gate( case OpType::SXdg: case OpType::Vdg: { apply_V(qbs.at(0), seg); - apply_X(qbs.at(0), seg); + apply_V(qbs.at(0), seg); + apply_V(qbs.at(0), seg); break; } case OpType::H: { - apply_H(qbs.at(0), seg); + apply_S(qbs.at(0), seg); + apply_V(qbs.at(0), seg); + apply_S(qbs.at(0), seg); break; } case OpType::CX: { @@ -286,19 +263,25 @@ 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_Z(qbs.at(1), seg); + apply_S(qbs.at(1), seg); + apply_S(qbs.at(1), seg); } else { apply_S(qbs.at(1), seg); - apply_Z(qbs.at(1), seg); + apply_S(qbs.at(1), seg); + apply_S(qbs.at(1), seg); apply_CX(qbs.at(0), qbs.at(1), seg); apply_S(qbs.at(1), seg); } break; } case OpType::CZ: { - apply_H(qbs.at(1), seg); + apply_S(qbs.at(1), seg); + apply_V(qbs.at(1), seg); + apply_S(qbs.at(1), seg); apply_CX(qbs.at(0), qbs.at(1), seg); - apply_H(qbs.at(1), seg); + apply_S(qbs.at(1), seg); + apply_V(qbs.at(1), seg); + apply_S(qbs.at(1), seg); break; } case OpType::ZZMax: { @@ -309,14 +292,16 @@ void ChoiMixTableau::apply_gate( } case OpType::ECR: { if (seg == TableauSegment::Input) { - apply_X(qbs.at(0), seg); + apply_V(qbs.at(0), seg); + apply_V(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_X(qbs.at(0), seg); + apply_V(qbs.at(0), seg); + apply_V(qbs.at(0), seg); apply_V(qbs.at(1), seg); } break; @@ -327,7 +312,8 @@ 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_Z(qbs.at(1), seg); + apply_S(qbs.at(1), seg); + apply_S(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); @@ -355,25 +341,28 @@ 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_.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_; } 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_.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_; } break; } @@ -410,10 +399,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; } @@ -421,7 +410,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 @@ -434,7 +423,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); @@ -457,7 +446,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); @@ -475,7 +464,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); @@ -498,7 +487,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); @@ -523,13 +512,14 @@ 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_.xmat_.conservativeResize(n_rows - 1, n_cols); + tab_.zmat_.conservativeResize(n_rows - 1, n_cols); + tab_.phase_.conservativeResize(n_rows - 1); + --tab_.n_rows_; } void ChoiMixTableau::remove_col(unsigned col) { @@ -540,11 +530,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); @@ -552,6 +542,7 @@ 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) { @@ -588,10 +579,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; } @@ -629,12 +620,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 d2b373e38f..45ba0011db 100644 --- a/tket/src/Clifford/SymplecticTableau.cpp +++ b/tket/src/Clifford/SymplecticTableau.cpp @@ -62,51 +62,56 @@ const std::map, std::pair> SymplecticTableau::SymplecticTableau( const MatrixXb &xmat, const MatrixXb &zmat, const VectorXb &phase) - : xmat(xmat), zmat(zmat), phase(phase) { - if (zmat.rows() != xmat.rows() || phase.size() != xmat.rows()) + : n_rows_(xmat.rows()), + n_qubits_(xmat.cols()), + xmat_(xmat), + zmat_(zmat), + phase_(phase) { + if (zmat.rows() != n_rows_ || phase_.size() != n_rows_) throw std::invalid_argument( "Tableau must have the same number of rows in each component."); - if (zmat.cols() != xmat.cols()) + if (zmat.cols() != n_qubits_) throw std::invalid_argument( "Tableau must have the same number of columns in x and z components."); } SymplecticTableau::SymplecticTableau(const PauliStabiliserVec &rows) { - 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) { + 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) { 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 xmat.rows(); } -unsigned SymplecticTableau::get_n_qubits() const { return xmat.cols(); } +unsigned SymplecticTableau::get_n_rows() const { return n_rows_; } +unsigned SymplecticTableau::get_n_qubits() const { return n_qubits_; } PauliStabiliser SymplecticTableau::get_pauli(unsigned i) const { - 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(); + 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.get_n_rows(); ++i) { - os << tab.xmat.row(i) << " " << tab.zmat.row(i) << " " << tab.phase(i) + for (unsigned i = 0; i < tab.n_rows_; ++i) { + os << tab.xmat_.row(i) << " " << tab.zmat_.row(i) << " " << tab.phase_(i) << std::endl; } return os; @@ -115,58 +120,40 @@ 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->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); + 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_); } 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); -} - -void SymplecticTableau::apply_Z(unsigned qb) { - for (unsigned i = 0; i < get_n_rows(); ++i) phase(i) = phase(i) ^ xmat(i, qb); + MatrixXb::ColXpr xcol = xmat_.col(qb); + MatrixXb::ColXpr zcol = zmat_.col(qb); + col_mult(xcol, zcol, false, zcol, phase_); } 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); -} - -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; - } + MatrixXb::ColXpr xcol = xmat_.col(qb); + MatrixXb::ColXpr zcol = zmat_.col(qb); + col_mult(zcol, xcol, true, xcol, phase_); } 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 < 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); + 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); } } @@ -174,16 +161,20 @@ void SymplecticTableau::apply_gate( OpType type, const std::vector &qbs) { switch (type) { case OpType::Z: { - apply_Z(qbs.at(0)); + apply_S(qbs.at(0)); + apply_S(qbs.at(0)); break; } case OpType::X: { - apply_X(qbs.at(0)); + apply_V(qbs.at(0)); + apply_V(qbs.at(0)); break; } case OpType::Y: { - apply_Z(qbs.at(0)); - apply_X(qbs.at(0)); + apply_S(qbs.at(0)); + apply_S(qbs.at(0)); + apply_V(qbs.at(0)); + apply_V(qbs.at(0)); break; } case OpType::S: { @@ -192,22 +183,24 @@ void SymplecticTableau::apply_gate( } case OpType::Sdg: { apply_S(qbs.at(0)); - apply_Z(qbs.at(0)); + apply_S(qbs.at(0)); + apply_S(qbs.at(0)); break; } - case OpType::V: - case OpType::SX: { + case OpType::V: { apply_V(qbs.at(0)); break; } - case OpType::Vdg: - case OpType::SXdg: { + case OpType::Vdg: { + 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_H(qbs.at(0)); + apply_S(qbs.at(0)); + apply_V(qbs.at(0)); + apply_S(qbs.at(0)); break; } case OpType::CX: { @@ -216,15 +209,20 @@ void SymplecticTableau::apply_gate( } case OpType::CY: { apply_S(qbs.at(1)); - apply_Z(qbs.at(1)); + apply_S(qbs.at(1)); + apply_S(qbs.at(1)); apply_CX(qbs.at(0), qbs.at(1)); apply_S(qbs.at(1)); break; } case OpType::CZ: { - apply_H(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_H(qbs.at(1)); + apply_S(qbs.at(1)); + apply_V(qbs.at(1)); + apply_S(qbs.at(1)); break; } case OpType::SWAP: { @@ -237,34 +235,6 @@ void SymplecticTableau::apply_gate( apply_CX(qbs.at(0), qbs.at(2)); break; } - case OpType::ZZMax: { - apply_H(qbs.at(1)); - apply_S(qbs.at(0)); - apply_V(qbs.at(1)); - apply_CX(qbs.at(0), qbs.at(1)); - apply_H(qbs.at(1)); - break; - } - case OpType::ECR: { - apply_S(qbs.at(0)); - 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_V(qbs.at(0)); - apply_V(qbs.at(1)); - apply_CX(qbs.at(0), qbs.at(1)); - apply_V(qbs.at(0)); - apply_S(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::noop: case OpType::Phase: { break; @@ -279,8 +249,7 @@ void SymplecticTableau::apply_gate( void SymplecticTableau::apply_pauli_gadget( const PauliStabiliser &pauli, unsigned half_pis) { - unsigned n_qubits = get_n_qubits(); - if (pauli.string.size() != 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"); @@ -312,40 +281,39 @@ 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_flip = pauli.is_real_negative() ^ (half_pis == 3); + bool phase = pauli.is_real_negative() ^ (half_pis == 3); - for (unsigned i = 0; i < get_n_rows(); ++i) { + for (unsigned i = 0; i < 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_flip, - i_, xr, zr, phase(i)); + xr, zr, phase_(i), pauli_xrow.row(0), pauli_zrow.row(0), phase, i_, + xr, zr, phase_(i)); } } } MatrixXb SymplecticTableau::anticommuting_rows() const { - unsigned n_rows = get_n_rows(); - MatrixXb res = MatrixXb::Zero(n_rows, n_rows); - for (unsigned i = 0; i < n_rows; ++i) { + 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 < get_n_qubits(); ++q) { - anti ^= (xmat(i, q) && zmat(j, q)); - anti ^= (xmat(j, q) && zmat(i, q)); + for (unsigned q = 0; q < 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; @@ -359,33 +327,32 @@ unsigned SymplecticTableau::rank() const { SymplecticTableau copy(*this); copy.gaussian_form(); unsigned empty_rows = 0; - 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()) + 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 < get_n_rows(); ++i) { + for (unsigned i = 0; i < n_rows_; ++i) { unsigned sum = 0; - for (unsigned j = 0; j < get_n_qubits(); ++j) { - if (xmat(i, j) && zmat(i, j)) ++sum; + for (unsigned j = 0; j < 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(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; + 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_; std::vector> row_ops = gaussian_elimination_row_ops(fullmat); for (const std::pair &op : row_ops) { @@ -399,7 +366,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 < get_n_qubits(); i++) { + for (unsigned i = 0; i < n_qubits_; i++) { std::pair res = BoolPauli::mult_lut.at({{xa(i), za(i)}, {xb(i), zb(i)}}); xw(i) = res.first.x; @@ -412,18 +379,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 < get_n_rows(); i++) { + for (unsigned i = 0; i < 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.get_n_rows(); - j["nqubits"] = tab.get_n_qubits(); - j["xmat"] = tab.xmat; - j["zmat"] = tab.zmat; - j["phase"] = tab.phase; + j["nrows"] = tab.n_rows_; + j["nqubits"] = tab.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 80b9dffd53..4f0527c949 100644 --- a/tket/src/Clifford/UnitaryTableau.cpp +++ b/tket/src/Clifford/UnitaryTableau.cpp @@ -155,16 +155,6 @@ 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_); @@ -175,31 +165,6 @@ 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); @@ -219,16 +184,20 @@ void UnitaryTableau::apply_gate_at_front( OpType type, const qubit_vector_t& qbs) { switch (type) { case OpType::Z: { - apply_Z_at_front(qbs.at(0)); + apply_S_at_front(qbs.at(0)); + apply_S_at_front(qbs.at(0)); break; } case OpType::X: { - apply_X_at_front(qbs.at(0)); + apply_V_at_front(qbs.at(0)); + apply_V_at_front(qbs.at(0)); break; } case OpType::Y: { - apply_Z_at_front(qbs.at(0)); - apply_X_at_front(qbs.at(0)); + 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)); break; } case OpType::S: { @@ -237,22 +206,24 @@ void UnitaryTableau::apply_gate_at_front( } case OpType::Sdg: { apply_S_at_front(qbs.at(0)); - apply_Z_at_front(qbs.at(0)); + apply_S_at_front(qbs.at(0)); + apply_S_at_front(qbs.at(0)); break; } - case OpType::V: - case OpType::SX: { + case OpType::V: { apply_V_at_front(qbs.at(0)); break; } - case OpType::Vdg: - case OpType::SXdg: { + case OpType::Vdg: { + apply_V_at_front(qbs.at(0)); + apply_V_at_front(qbs.at(0)); apply_V_at_front(qbs.at(0)); - apply_X_at_front(qbs.at(0)); break; } case OpType::H: { - apply_H_at_front(qbs.at(0)); + apply_S_at_front(qbs.at(0)); + apply_V_at_front(qbs.at(0)); + apply_S_at_front(qbs.at(0)); break; } case OpType::CX: { @@ -263,13 +234,18 @@ 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_Z_at_front(qbs.at(1)); + apply_S_at_front(qbs.at(1)); + apply_S_at_front(qbs.at(1)); break; } case OpType::CZ: { - apply_H_at_front(qbs.at(1)); + apply_S_at_front(qbs.at(1)); + 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_H_at_front(qbs.at(1)); + apply_S_at_front(qbs.at(1)); + apply_V_at_front(qbs.at(1)); + apply_S_at_front(qbs.at(1)); break; } case OpType::SWAP: { @@ -282,34 +258,6 @@ void UnitaryTableau::apply_gate_at_front( apply_CX_at_front(qbs.at(0), qbs.at(2)); 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_CX_at_front(qbs.at(0), 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::noop: case OpType::Phase: { break; @@ -435,8 +383,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; @@ -451,9 +399,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; @@ -474,14 +422,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; @@ -498,13 +446,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; @@ -576,14 +524,6 @@ 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); } @@ -592,22 +532,6 @@ 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); @@ -620,54 +544,14 @@ void UnitaryRevTableau::apply_CX_at_end( void UnitaryRevTableau::apply_gate_at_front( OpType type, const qubit_vector_t& 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; - } - } + if (type != OpType::Phase) + tab_.apply_gate_at_end(get_op_ptr(type)->dagger()->get_type(), qbs); } void UnitaryRevTableau::apply_gate_at_end( OpType type, const qubit_vector_t& 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; - } - } + if (type != OpType::Phase) + tab_.apply_gate_at_front(get_op_ptr(type)->dagger()->get_type(), qbs); } void UnitaryRevTableau::apply_pauli_at_front( @@ -709,16 +593,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" + 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" + 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 450cd9208c..5a57e16cc8 100644 --- a/tket/src/Converters/ChoiMixTableauConverters.cpp +++ b/tket/src/Converters/ChoiMixTableauConverters.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include "tket/Converters/Converters.hpp" #include "tket/Diagonalisation/Diagonalisation.hpp" @@ -29,729 +30,637 @@ 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; } -struct ChoiMixBuilder { +std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { /** - * 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 + * 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). */ - 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; - /** - * STAGES OF SYNTHESIS - */ + // Operate on a copy of the tableau to track the remainder as we extract gates + ChoiMixTableau tab(t); - // 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(); -} - -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(); -} + // 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(); -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) { + // 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; 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) - in_circ.add_qubit(key.first); + input_qubits.push_back(key.first); else - out_circ_tp.add_qubit(key.first); + output_qubits.push_back(key.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"); + 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& 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"); + 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); } -} -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}); + // 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); } - out_tab.rename_qubits( - out_in_permutation, ChoiMixTableau::TableauSegment::Output); - return ChoiMixTableau::compose(ChoiMixTableau::compose(in_tab, tab), out_tab); -} + // 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}); + } + + // 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(); - 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; + // 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; - // 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); + // 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; + } } - 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); + + 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; + } } - // 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); + // 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; + } + } } - // 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); + // 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); } - 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); + 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; + } + } } - // 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); + } + + // 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; } - solved_ins.insert({in_diag_circ.second}); - solved_outs.insert({out_diag_circ_dag.second}); - break; } - } + 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; + } + } + } - // 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); -} + // 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; + } + } + } -// 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); + // 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 + 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; } } } } - 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; -} -void ChoiMixBuilder::diagonalise_segments() { - // Canonicalise tableau + // 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 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); + } tab.canonical_column_order(ChoiMixTableau::TableauSegment::Output); - tab.gaussian_form(); - // Set up diagonalisation tasks - std::list to_diag_ins, to_diag_outs; + // Only remaining rows must be completely over the outputs. Call + // diagonalisation methods to diagonalise coherent subspace + to_diag.clear(); for (unsigned r = 0; r < tab.get_n_rows(); ++r) { ChoiMixTableau::row_tensor_t rten = tab.get_row(r); - if (!rten.first.string.empty()) to_diag_ins.push_back(rten.first); - if (!rten.second.string.empty()) to_diag_outs.push_back(rten.second); + to_diag.push_back(rten.second); } - 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); + 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 output_qubits = tab.output_qubits(); - std::set diag_outs{output_qubits.begin(), output_qubits.end()}; Circuit out_diag_circ = - mutual_diagonalise(to_diag_outs, diag_outs, cx_config); + mutual_diagonalise(to_diag, diag_outs, CXConfigType::Tree); + // Extract the dagger of each gate in order from tab 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, - ChoiMixTableau::TableauSegment::Output); - } - - // 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; - } - 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) { - 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); + tab.apply_gate(com.get_op_ptr()->get_type(), qbs); + out_circ_tp.add_op(com.get_op_ptr()->transpose()->dagger(), qbs); } -} -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; + // 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); } - 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); + // Obtain CX instructions as column operations + col_ops = gaussian_elimination_col_ops(tab.tab_.zmat_); 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); + 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}); } - // Initialise rows - for (unsigned r = tab.get_n_rows(); r-- > n_collapsed;) { - // r always refers to the final row in the tableau + + // 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) { 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 initialisation identification in " - "ChoiMixTableau synthesis"); + "Unexpected error during zero initialisation in ChoiMixTableau " + "synthesis"); Qubit initialised_qb = row.second.string.begin()->first; - // 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); - } -} - -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); + out_circ.qubit_create(initialised_qb); + if (row.second.is_real_negative()) { + out_circ.add_op(OpType::X, {initialised_qb}); } - // 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 (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; + zero_initialised.insert(initialised_qb); } - 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; + // 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}); } - } - 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; - } + 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}); } } } - // 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); - } -} -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}); - } + // Initialise qubits with stabilizer rows and stitch subcircuits together out_circ.append(out_circ_tp.transpose()); - 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}; + in_circ.append_with_map(out_circ, join_permutation); + return {in_circ, join_permutation}; } } // namespace tket diff --git a/tket/src/Converters/PauliGadget.cpp b/tket/src/Converters/PauliGadget.cpp new file mode 100644 index 0000000000..451003776c --- /dev/null +++ b/tket/src/Converters/PauliGadget.cpp @@ -0,0 +1,381 @@ +// Copyright 2019-2023 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 4408c0b7e7..8f345432eb 100644 --- a/tket/src/Converters/PauliGraphConverters.cpp +++ b/tket/src/Converters/PauliGraphConverters.cpp @@ -15,6 +15,7 @@ #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 ba9d4fc936..32d45ea4b6 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,8 +64,9 @@ 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_X(i); - echelon.col(i) = tabl.zmat.block(size, i, size, 1); + tabl.apply_V(i); + tabl.apply_V(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()) { @@ -88,7 +89,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}); @@ -102,12 +103,13 @@ 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_Z(i); + tabl.apply_S(i); + tabl.apply_S(i); } } @@ -138,7 +140,8 @@ 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_Z(i); + tabl.apply_S(i); + tabl.apply_S(i); } /* @@ -147,7 +150,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}); @@ -161,7 +164,9 @@ Circuit unitary_tableau_to_circuit(const UnitaryTableau& tab) { */ for (unsigned i = 0; i < size; i++) { c.add_op(OpType::H, {i}); - tabl.apply_H(i); + tabl.apply_S(i); + tabl.apply_V(i); + tabl.apply_S(i); } /* @@ -170,12 +175,13 @@ 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_Z(i); + tabl.apply_S(i); + tabl.apply_S(i); } } @@ -204,7 +210,8 @@ 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_Z(i); + tabl.apply_S(i); + tabl.apply_S(i); } /* @@ -213,7 +220,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); } @@ -222,14 +229,15 @@ 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_Z(i); + tabl.apply_S(i); + tabl.apply_S(i); } - if (tabl.phase(i + size)) { + if (tabl.phase_(i + size)) { c.add_op(OpType::X, {i}); - tabl.apply_X(i); - ; + tabl.apply_V(i); + tabl.apply_V(i); } } diff --git a/tket/src/Diagonalisation/Diagonalisation.cpp b/tket/src/Diagonalisation/Diagonalisation.cpp index b712e4c2b3..354f522d3d 100644 --- a/tket/src/Diagonalisation/Diagonalisation.cpp +++ b/tket/src/Diagonalisation/Diagonalisation.cpp @@ -342,410 +342,4 @@ 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) { - /* - * 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 - */ - 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 { - 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}); - } - } - } - - /* - * Step 2.iv: Reduce pairs of mismatches to different qubits. - * Allow both gadgets to build a remaining qubit if it exists. - */ - std::optional last_mismatch = std::nullopt; - 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_mismatch = z_in_0; - } - } - - return {u, last_mismatch}; -} - -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/Transformations/PauliOptimisation.cpp b/tket/src/Transformations/PauliOptimisation.cpp index e393c18731..003c5e228c 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()) { - gadget_circ.append( - pauli_gadget_pair(pauli_gadgets[g], pauli_gadgets[g + 1], cx_config)); + append_pauli_gadget_pair( + gadget_circ, 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()) { - gadget_circ.append(pauli_gadget(pauli_gadgets[g], cx_config)); + append_single_pauli_gadget(gadget_circ, pauli_gadgets[g], cx_config); } // Stitch gadget circuit and Clifford circuit together circ = gadget_circ >> clifford_circ; diff --git a/tket/test/CMakeLists.txt b/tket/test/CMakeLists.txt index d5ff0bfee7..8d7600db81 100644 --- a/tket/test/CMakeLists.txt +++ b/tket/test/CMakeLists.txt @@ -122,7 +122,6 @@ 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/test_ChoiMixTableau.cpp b/tket/test/src/test_ChoiMixTableau.cpp index d8ea2f3247..c6b87d72ef 100644 --- a/tket/test/src/test_ChoiMixTableau.cpp +++ b/tket/test/src/test_ChoiMixTableau.cpp @@ -15,7 +15,6 @@ #include #include "testutil.hpp" -#include "tket/Circuit/Simulation/CircuitSimulator.hpp" #include "tket/Converters/Converters.hpp" namespace tket { @@ -61,12 +60,6 @@ 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( @@ -96,7 +89,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)}); + SpPauliStabiliser(Qubit(0), Pauli::Y, 2)}); THEN("Serialize and deserialize") { nlohmann::json j_tab = tab; ChoiMixTableau tab2{{}}; @@ -181,10 +174,12 @@ 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 + // UnitaryRevTableau (since lhs is transposed, +Y is flipped to -Y, and + // phase is returned on rhs) REQUIRE( - tab.get_row(2) == ChoiMixTableau::row_tensor_t{ - SpPauliStabiliser(Qubit(1), Pauli::Y), {}}); + tab.get_row(2) == + ChoiMixTableau::row_tensor_t{ + SpPauliStabiliser(Qubit(1), Pauli::Y), SpPauliStabiliser({}, 2)}); // Affecting the output segment should give the same effect as for // UnitaryTableau REQUIRE( @@ -202,8 +197,9 @@ 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), {}}); + tab.get_row(2) == + ChoiMixTableau::row_tensor_t{ + SpPauliStabiliser(Qubit(1), Pauli::Y), SpPauliStabiliser({}, 2)}); REQUIRE( tab.get_row(3) == ChoiMixTableau::row_tensor_t{ {}, SpPauliStabiliser(Qubit(2), Pauli::Y, 2)}); @@ -219,8 +215,9 @@ 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), {}}); + tab.get_row(2) == + ChoiMixTableau::row_tensor_t{ + SpPauliStabiliser(Qubit(1), Pauli::Y), SpPauliStabiliser({}, 2)}); REQUIRE( tab.get_row(3) == ChoiMixTableau::row_tensor_t{ {}, SpPauliStabiliser(Qubit(2), Pauli::Y, 2)}); @@ -452,21 +449,17 @@ 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_exact_circuit(tab).first; - REQUIRE(res == circ); - res = cm_tableau_to_unitary_extension_circuit(tab).first; + Circuit res = cm_tableau_to_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::Z, {2}); circ.add_op(OpType::X, {2}); + circ.add_op(OpType::Z, {2}); circ.add_op(OpType::Z, {3}); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - Circuit res = cm_tableau_to_exact_circuit(tab).first; - REQUIRE(res == circ); - res = cm_tableau_to_unitary_extension_circuit(tab).first; + Circuit res = cm_tableau_to_circuit(tab).first; REQUIRE(res == circ); } GIVEN("Iterate through single-qubit Cliffords with all entanglements") { @@ -487,9 +480,7 @@ 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_exact_circuit(tab).first; - Circuit res_uni = cm_tableau_to_unitary_extension_circuit(tab).first; - REQUIRE(res == res_uni); + Circuit res = cm_tableau_to_circuit(tab).first; ChoiMixTableau res_tab = circuit_to_cm_tableau(res); REQUIRE(res_tab == tab); REQUIRE(test_unitary_comparison(circ, res, true)); @@ -498,15 +489,10 @@ SCENARIO("Synthesis of circuits from ChoiMixTableaus") { GIVEN("A unitary circuit") { Circuit circ = get_test_circ(); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - 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); + Circuit res = cm_tableau_to_circuit(tab).first; + ChoiMixTableau res_tab = circuit_to_cm_tableau(res); REQUIRE(res_tab == tab); - REQUIRE(test_unitary_comparison(circ, res.first, true)); + REQUIRE(test_unitary_comparison(circ, res, true)); } GIVEN("Check unitary equivalence by calculating matrix") { Circuit circ(4); @@ -515,69 +501,21 @@ 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); - 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); - } + Circuit res = cm_tableau_to_circuit(tab).first; + REQUIRE(test_unitary_comparison(circ, res, true)); } 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_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); + 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(); 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(); @@ -585,14 +523,9 @@ SCENARIO("Synthesis of circuits from ChoiMixTableaus") { circ.add_op(OpType::Collapse, {i}); } ChoiMixTableau tab = circuit_to_cm_tableau(circ); - Circuit res = cm_tableau_to_exact_circuit(tab).first; + Circuit res = cm_tableau_to_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(); @@ -601,26 +534,18 @@ SCENARIO("Synthesis of circuits from ChoiMixTableaus") { } circ.qubit_discard(Qubit(0)); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - std::pair res = cm_tableau_to_exact_circuit(tab); + std::pair res = cm_tableau_to_circuit(tab); ChoiMixTableau res_tab = circuit_to_cm_tableau(res.first); qubit_map_t perm; - for (const std::pair& p : res.second) { - perm.insert({p.second, p.first}); + for (const std::pair& p : res.second) { + perm.insert({Qubit(p.second), Qubit(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); @@ -639,24 +564,13 @@ 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); - 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); + 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(); 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); @@ -672,36 +586,18 @@ 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_exact_circuit(tab); + std::pair res = cm_tableau_to_circuit(tab); ChoiMixTableau res_tab = circuit_to_cm_tableau(res.first); qubit_map_t perm; - for (const std::pair& p : res.second) { - perm.insert({p.second, p.first}); + for (const std::pair& p : res.second) { + perm.insert({Qubit(p.second), Qubit(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); @@ -719,211 +615,18 @@ 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_exact_circuit(tab); + std::pair res = cm_tableau_to_circuit(tab); ChoiMixTableau res_tab = circuit_to_cm_tableau(res.first); qubit_map_t perm; - for (const std::pair& p : res.second) { - perm.insert({p.second, p.first}); + for (const std::pair& p : res.second) { + perm.insert({Qubit(p.second), Qubit(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); - 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_Diagonalisation.cpp b/tket/test/src/test_Diagonalisation.cpp deleted file mode 100644 index 990d9695ca..0000000000 --- a/tket/test/src/test_Diagonalisation.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2019-2023 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)); - } - } - } - } - } -} - -} // namespace test_Diagonalisation -} // namespace tket diff --git a/tket/test/src/test_PauliGraph.cpp b/tket/test/src/test_PauliGraph.cpp index 6e85b53252..c8d4758dc4 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,14 +920,13 @@ SCENARIO("Diagonalise a pair of gadgets") { Circuit correct; for (unsigned i = 0; i < 2; ++i) { - correct.append(pauli_gadget(gadgets.at(i))); + append_single_pauli_gadget(correct, gadgets.at(i)); } auto u_correct = tket_sim::get_unitary(correct); GIVEN("Snake configuration") { CXConfigType config = CXConfigType::Snake; - - circ.append(pauli_gadget_pair(gadgets.at(0), gadgets.at(1), config)); + append_pauli_gadget_pair(circ, 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); @@ -935,7 +934,7 @@ SCENARIO("Diagonalise a pair of gadgets") { } GIVEN("Star configuration") { CXConfigType config = CXConfigType::Star; - circ.append(pauli_gadget_pair(gadgets.at(0), gadgets.at(1), config)); + append_pauli_gadget_pair(circ, 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); @@ -943,7 +942,7 @@ SCENARIO("Diagonalise a pair of gadgets") { } GIVEN("Tree configuration") { CXConfigType config = CXConfigType::Tree; - circ.append(pauli_gadget_pair(gadgets.at(0), gadgets.at(1), config)); + append_pauli_gadget_pair(circ, 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); @@ -951,11 +950,10 @@ SCENARIO("Diagonalise a pair of gadgets") { } GIVEN("MultiQGate configuration") { CXConfigType config = CXConfigType::MultiQGate; - circ.append(pauli_gadget_pair(gadgets.at(0), gadgets.at(1), config)); - THEN( - "XXPhase3 were used for both mutual reduction and individual gadgets") { - Transforms::decomp_boxes().apply(circ); - REQUIRE(circ.count_gates(OpType::XXPhase3) == 4); + append_pauli_gadget_pair(circ, gadgets.at(0), gadgets.at(1), config); + circ.decompose_boxes_recursively(); + THEN("XXPhase3 were used") { + REQUIRE(circ.count_gates(OpType::XXPhase3) == 2); } THEN("Unitary is correct") { auto u_res = tket_sim::get_unitary(circ); diff --git a/tket/test/src/test_PhaseGadget.cpp b/tket/test/src/test_PhaseGadget.cpp index 302ff95155..cf02d862d9 100644 --- a/tket/test/src/test_PhaseGadget.cpp +++ b/tket/test/src/test_PhaseGadget.cpp @@ -149,8 +149,7 @@ 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(SpSymPauliTensor(DensePauliMap{Pauli::X, Pauli::Y}, t)); + Circuit circ = pauli_gadget({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); @@ -287,7 +286,7 @@ SCENARIO("Decompose phase gadgets") { for (unsigned i = 0; i < n_qubits; ++i) { nZ.push_back(Pauli::Z); } - Circuit pauli_gadget_circ = pauli_gadget(SpSymPauliTensor(nZ, 0.2)); + Circuit pauli_gadget_circ = pauli_gadget(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_UnitaryTableau.cpp b/tket/test/src/test_UnitaryTableau.cpp index b9a0a9d20d..68cd333c23 100644 --- a/tket/test/src/test_UnitaryTableau.cpp +++ b/tket/test/src/test_UnitaryTableau.cpp @@ -16,7 +16,6 @@ #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" @@ -247,59 +246,11 @@ 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)}); @@ -308,15 +259,11 @@ 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); @@ -350,21 +297,6 @@ 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(); @@ -427,22 +359,6 @@ 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") { @@ -502,52 +418,14 @@ 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); @@ -643,18 +521,6 @@ SCENARIO("Synthesis of circuits from UnitaryRevTableau") { UnitaryRevTableau res_tab = circuit_to_unitary_rev_tableau(res); REQUIRE(res_tab == tab); } - GIVEN("Gate coverage for OpTypes without daggers") { - UnitaryRevTableau tab(3); - tab.apply_gate_at_end(OpType::ZZMax, {Qubit(0), Qubit(1)}); - tab.apply_gate_at_end(OpType::ISWAPMax, {Qubit(1), Qubit(2)}); - UnitaryRevTableau rev_tab(3); - rev_tab.apply_gate_at_front(OpType::ISWAPMax, {Qubit(1), Qubit(2)}); - rev_tab.apply_gate_at_front(OpType::ZZMax, {Qubit(0), Qubit(1)}); - REQUIRE(tab == rev_tab); - Circuit res = unitary_rev_tableau_to_circuit(tab); - UnitaryRevTableau res_tab = circuit_to_unitary_rev_tableau(res); - REQUIRE(tab == res_tab); - } } SCENARIO("UnitaryTableauBoxes in Circuits") { From 0594427e1e68d2d2701bba62486bf612b6795780 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Thu, 4 Jan 2024 14:21:17 +0000 Subject: [PATCH 35/35] Use PEP 600 manylinux containers to build pytket (#1194) --- .github/workflows/build_libs.yml | 4 ++-- .github/workflows/packages.yml | 8 +++---- .github/workflows/release.yml | 10 ++++----- conan-profiles/linux-armv8-gcc10-libstdc++ | 8 ------- conan-profiles/linux-armv8-gcc12 | 8 +++++++ conan-profiles/linux-x86_64-gcc10-libstdc++ | 8 ------- conan-profiles/linux-x86_64-gcc12 | 8 +++++++ tket/src/Circuit/Boxes.cpp | 10 ++++++--- tket/src/Predicates/CompilationUnit.cpp | 23 +++++++++++---------- 9 files changed, 46 insertions(+), 41 deletions(-) delete mode 100644 conan-profiles/linux-armv8-gcc10-libstdc++ create mode 100644 conan-profiles/linux-armv8-gcc12 delete mode 100644 conan-profiles/linux-x86_64-gcc10-libstdc++ create mode 100644 conan-profiles/linux-x86_64-gcc12 diff --git a/.github/workflows/build_libs.yml b/.github/workflows/build_libs.yml index 0430bfd22c..39bb2f9f81 100644 --- a/.github/workflows/build_libs.yml +++ b/.github/workflows/build_libs.yml @@ -107,7 +107,7 @@ jobs: - uses: actions/checkout@v4 - name: set up container run: | - docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux2014_x86_64:latest /bin/bash + docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux_2_28_x86_64:latest /bin/bash docker cp ./libs/${{ matrix.lib }} linux_build:/ docker cp ./libver linux_build:/ docker cp ./.github/workflows/linuxbuildlib linux_build:/ @@ -123,6 +123,6 @@ jobs: UPLOAD_PACKAGE=${UPLOAD_PACKAGE} JFROG_ARTIFACTORY_TOKEN_3=${{ secrets.JFROG_ARTIFACTORY_TOKEN_3 }} JFROG_ARTIFACTORY_USER_3=${{ secrets.JFROG_ARTIFACTORY_USER_3 }} - CONAN_PROFILE=linux-x86_64-gcc10-libstdc++ + CONAN_PROFILE=linux-x86_64-gcc12 EOF docker exec --env-file env-vars linux_build /bin/bash -c "/linuxbuildlib" diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 55f1c56cbe..70feff8c03 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -58,13 +58,13 @@ jobs: - name: Set up container run: | - docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux2014_x86_64:latest /bin/bash + docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux_2_28_x86_64:latest /bin/bash docker cp . linux_build:/tket/ - name: Install and upload packages run: | docker start linux_build - docker exec -e JFROG_ARTIFACTORY_TOKEN_3="${{ secrets.JFROG_ARTIFACTORY_TOKEN_3 }}" -e JFROG_ARTIFACTORY_USER_3="${{ secrets.JFROG_ARTIFACTORY_USER_3 }}" -e CONAN_PROFILE=linux-x86_64-gcc10-libstdc++ linux_build /bin/bash -c "/tket/.github/workflows/linuxbuildpackages" + docker exec -e JFROG_ARTIFACTORY_TOKEN_3="${{ secrets.JFROG_ARTIFACTORY_TOKEN_3 }}" -e JFROG_ARTIFACTORY_USER_3="${{ secrets.JFROG_ARTIFACTORY_USER_3 }}" -e CONAN_PROFILE=linux-x86_64-gcc12 linux_build /bin/bash -c "/tket/.github/workflows/linuxbuildpackages" build_manylinux_aarch64: name: Build on manylinux (aarch64) @@ -77,14 +77,14 @@ jobs: run: | export DOCKER_HOST=unix://${HOME}/.docker/run/docker.sock docker rm --force -v linux_build - docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux2014_aarch64:latest /bin/bash + docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux_2_28_aarch64:latest /bin/bash docker cp . linux_build:/tket/ - name: Install and upload packages run: | export DOCKER_HOST=unix://${HOME}/.docker/run/docker.sock docker start linux_build - docker exec -e JFROG_ARTIFACTORY_TOKEN_3="${{ secrets.JFROG_ARTIFACTORY_TOKEN_3 }}" -e JFROG_ARTIFACTORY_USER_3="${{ secrets.JFROG_ARTIFACTORY_USER_3 }}" -e CONAN_PROFILE=linux-armv8-gcc10-libstdc++ linux_build /bin/bash -c "/tket/.github/workflows/linuxbuildpackages" + docker exec -e JFROG_ARTIFACTORY_TOKEN_3="${{ secrets.JFROG_ARTIFACTORY_TOKEN_3 }}" -e JFROG_ARTIFACTORY_USER_3="${{ secrets.JFROG_ARTIFACTORY_USER_3 }}" -e CONAN_PROFILE=linux-armv8-gcc12 linux_build /bin/bash -c "/tket/.github/workflows/linuxbuildpackages" - name: Remove container if: always() diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index df3add5a54..f0a10bf266 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,12 +22,12 @@ jobs: - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Set up container run: | - docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux2014_x86_64:latest /bin/bash + docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux_2_28_x86_64:latest /bin/bash docker cp . linux_build:/tket/ - name: Run build run: | docker start linux_build - docker exec -e PY_TAG="cp3${{ matrix.python3-version }}-cp3${{ matrix.python3-version }}" -e CONAN_PROFILE=linux-x86_64-gcc10-libstdc++ linux_build /bin/bash -c "/tket/.github/workflows/linuxbuildwheel" + docker exec -e PY_TAG="cp3${{ matrix.python3-version }}-cp3${{ matrix.python3-version }}" -e CONAN_PROFILE=linux-x86_64-gcc12 linux_build /bin/bash -c "/tket/.github/workflows/linuxbuildwheel" mkdir wheelhouse docker cp linux_build:/tket/pytket/audited/. wheelhouse/ - uses: actions/upload-artifact@v4 @@ -49,13 +49,13 @@ jobs: - name: Set up container run: | export DOCKER_HOST=unix://${HOME}/.docker/run/docker.sock - docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux2014_aarch64:latest /bin/bash + docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux_2_28_aarch64:latest /bin/bash docker cp . linux_build:/tket/ - name: Run build run: | export DOCKER_HOST=unix://${HOME}/.docker/run/docker.sock docker start linux_build - docker exec -e PY_TAG="cp3${{ matrix.python3-version }}-cp3${{ matrix.python3-version }}" -e CONAN_PROFILE=linux-armv8-gcc10-libstdc++ linux_build /bin/bash -c "/tket/.github/workflows/linuxbuildwheel" + docker exec -e PY_TAG="cp3${{ matrix.python3-version }}-cp3${{ matrix.python3-version }}" -e CONAN_PROFILE=linux-armv8-gcc12 linux_build /bin/bash -c "/tket/.github/workflows/linuxbuildwheel" mkdir wheelhouse docker cp linux_build:/tket/pytket/audited/. wheelhouse/ - name: Remove container @@ -247,7 +247,7 @@ jobs: - name: Set up container run: | export DOCKER_HOST=unix://${HOME}/.docker/run/docker.sock - docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux2014_aarch64:latest /bin/bash + docker create --name linux_build -i -v /:/host quay.io/pypa/manylinux_2_28_aarch64:latest /bin/bash docker cp . linux_build:/tket/ - name: Run tests run: | diff --git a/conan-profiles/linux-armv8-gcc10-libstdc++ b/conan-profiles/linux-armv8-gcc10-libstdc++ deleted file mode 100644 index f94db73ef8..0000000000 --- a/conan-profiles/linux-armv8-gcc10-libstdc++ +++ /dev/null @@ -1,8 +0,0 @@ -[settings] -arch=armv8 -build_type=Release -compiler=gcc -compiler.cppstd=gnu14 -compiler.libcxx=libstdc++ -compiler.version=10 -os=Linux diff --git a/conan-profiles/linux-armv8-gcc12 b/conan-profiles/linux-armv8-gcc12 new file mode 100644 index 0000000000..3bb763ca7a --- /dev/null +++ b/conan-profiles/linux-armv8-gcc12 @@ -0,0 +1,8 @@ +[settings] +arch=armv8 +build_type=Release +compiler=gcc +compiler.cppstd=gnu17 +compiler.libcxx=libstdc++11 +compiler.version=12 +os=Linux diff --git a/conan-profiles/linux-x86_64-gcc10-libstdc++ b/conan-profiles/linux-x86_64-gcc10-libstdc++ deleted file mode 100644 index fbfc138849..0000000000 --- a/conan-profiles/linux-x86_64-gcc10-libstdc++ +++ /dev/null @@ -1,8 +0,0 @@ -[settings] -arch=x86_64 -build_type=Release -compiler=gcc -compiler.cppstd=gnu14 -compiler.libcxx=libstdc++ -compiler.version=10 -os=Linux diff --git a/conan-profiles/linux-x86_64-gcc12 b/conan-profiles/linux-x86_64-gcc12 new file mode 100644 index 0000000000..99c0c1f7ea --- /dev/null +++ b/conan-profiles/linux-x86_64-gcc12 @@ -0,0 +1,8 @@ +[settings] +arch=x86_64 +build_type=Release +compiler=gcc +compiler.cppstd=gnu17 +compiler.libcxx=libstdc++11 +compiler.version=12 +os=Linux diff --git a/tket/src/Circuit/Boxes.cpp b/tket/src/Circuit/Boxes.cpp index e25d4ea83b..5aabc204e4 100644 --- a/tket/src/Circuit/Boxes.cpp +++ b/tket/src/Circuit/Boxes.cpp @@ -328,10 +328,14 @@ std::string CustomGate::get_name(bool) const { s << gate_->get_name(); if (!params_.empty()) { s << "("; - std::string sep = ""; + bool initial = true; for (const Expr &e : params_) { - s << sep << e; - sep = ","; + if (initial) { + s << e; + } else { + s << "," << e; + } + initial = false; } s << ")"; } diff --git a/tket/src/Predicates/CompilationUnit.cpp b/tket/src/Predicates/CompilationUnit.cpp index 54ef9b5f25..b1158bd835 100644 --- a/tket/src/Predicates/CompilationUnit.cpp +++ b/tket/src/Predicates/CompilationUnit.cpp @@ -56,27 +56,28 @@ bool CompilationUnit::check_all_predicates() const { } std::string CompilationUnit::to_string() const { - std::string str = "~~~CompilationUnit~~~\n\n"; + std::stringstream s; + s << "~~~CompilationUnit~~~" << std::endl + << "" << std::endl; if (!target_preds.empty()) { - str += "Target Predicates:\n"; + s << "Target Predicates:" << std::endl; for (const TypePredicatePair& pp : target_preds) { - str += (" " + pp.second->to_string() + "\n"); + s << " " << pp.second->to_string() << std::endl; } } else - str += "Target Predicates empty\n"; + s << "Target Predicates empty" << std::endl; if (!cache_.empty()) { - str += "Cache:\n"; + s << "Cache:" << std::endl; for (const std::pair>& tp : cache_) { - str += (" " + tp.second.first->to_string() + " :: "); - str += tp.second.second ? "True\n" : "False\n"; + s << " " << tp.second.first->to_string() + << " :: " << ((tp.second.second) ? "True" : "False") << std::endl; } } else - str += "Cache empty\n"; - return str; + s << "Cache empty" << std::endl; + return s.str(); } void CompilationUnit::empty_cache() const { cache_ = {}; }