From f44d8ac5ec6e272809a542ef43093830dccd4241 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 May 2024 21:29:28 -0400 Subject: [PATCH 01/66] CompatHelper: bump compat for Makie in [weakdeps] to 0.21, (keep existing compat) (#281) Co-authored-by: CompatHelper Julia --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 4296812b8..15c21efa5 100644 --- a/Project.toml +++ b/Project.toml @@ -52,7 +52,7 @@ InteractiveUtils = "1.9" LDPCDecoders = "0.3.1" LinearAlgebra = "1.9" MacroTools = "0.5.9" -Makie = "0.20" +Makie = "0.20, 0.21" Nemo = "0.42, 0.43, 0.44, 0.45" Plots = "1.38.0" PrecompileTools = "1.2" From c1d115d3ee8a929279b724df1cc63fd00a9b255a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 07:43:26 -0400 Subject: [PATCH 02/66] Bump dawidd6/action-download-artifact from 3 to 5 (#288) Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 3 to 5. - [Release notes](https://github.com/dawidd6/action-download-artifact/releases) - [Commits](https://github.com/dawidd6/action-download-artifact/compare/v3...v5) --- updated-dependencies: - dependency-name: dawidd6/action-download-artifact 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/benchmark-comment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmark-comment.yml b/.github/workflows/benchmark-comment.yml index e873dbfaf..247d792e5 100644 --- a/.github/workflows/benchmark-comment.yml +++ b/.github/workflows/benchmark-comment.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v4 # restore records from the artifacts - - uses: dawidd6/action-download-artifact@v3 + - uses: dawidd6/action-download-artifact@v5 with: workflow: benchmark.yml name: performance-tracking From c64492bebd974389976616a55347f5e6a1f71ecb Mon Sep 17 00:00:00 2001 From: Rabqubit Date: Fri, 14 Jun 2024 21:51:30 +0800 Subject: [PATCH 03/66] Concatenated quantum code (#289) --------- Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 4 ++- docs/src/references.bib | 9 ++++++- docs/src/references.md | 1 + src/ecc/ECC.jl | 10 +++++-- src/ecc/codes/concat.jl | 47 +++++++++++++++++++++++++++++++++ test/test_ecc_base.jl | 3 ++- test/test_ecc_codeproperties.jl | 2 +- 7 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 src/ecc/codes/concat.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 0232077d8..c6d5cc47c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,10 @@ # News -## v0.9.4 - dev +## v0.9.4 - 2024-06-14 +- Addition of a constructor for concatenated quantum codes -- `Concat`. +- Addition of multiple unexported classical code constructors. - Gate errors are now conveniently supported by the various ECC benchmark setups in the `ECC` module. - Remove printing of spurious debug info from the PyBP decoder. - Significant improvements to the low-level circuit compiler (the sumtype compactifier), leading to faster Pauli frame simulation of noisy circuits. diff --git a/docs/src/references.bib b/docs/src/references.bib index f67523219..c492d0ea7 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -378,4 +378,11 @@ @book{error2024lin author={Lin, Shu and Costello, Daniel}, year={2024}, publisher={Pearson} -} \ No newline at end of file +} + +@article{knill1996concatenated, + title={Concatenated quantum codes}, + author={Knill, Emanuel and Laflamme, Raymond}, + journal={arXiv preprint quant-ph/9608012}, + year={1996} +} diff --git a/docs/src/references.md b/docs/src/references.md index f4d3fc0b2..b37e94d4e 100644 --- a/docs/src/references.md +++ b/docs/src/references.md @@ -36,6 +36,7 @@ For quantum code construction routines: - [chao2018quantum](@cite) - [kitaev2003fault](@cite) - [fowler2012surface](@cite) +- [knill1996concatenated](@cite) For classical code construction routines: - [muller1954application](@cite) diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 0e6216eb7..bae3df389 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -8,7 +8,7 @@ using DocStringExtensions using Combinatorics: combinations using SparseArrays: sparse using Statistics: std -using Nemo: ZZ, residue_ring, matrix, finite_field, GF, minpoly, coeff, lcm, FqPolyRingElem, FqFieldElem, is_zero, degree, defining_polynomial, is_irreducible +using Nemo: ZZ, residue_ring, matrix, finite_field, GF, minpoly, coeff, lcm, FqPolyRingElem, FqFieldElem, is_zero, degree, defining_polynomial, is_irreducible abstract type AbstractECC end @@ -19,7 +19,7 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, RepCode, CSS, Shor9, Steane7, Cleve8, Perfect5, Bitflip3, - Toric, Gottesman, Surface, + Toric, Gottesman, Surface, Concat, evaluate_decoder, CommutationCheckECCSetup, NaiveSyndromeECCSetup, ShorSyndromeECCSetup, TableDecoder, @@ -49,6 +49,11 @@ function parity_checks_z(code::AbstractECC) throw(lazy"Codes of type $(typeof(code)) do not have separate X and Z parity checks, either because they are not a CSS code and thus inherently do not have separate checks, or because its separate checks are not yet implemented in this library.") end + +"""Check if the code is CSS. + +Return `nothing` if unknown from the type. +""" function iscss(::Type{T}) where T<:AbstractECC return false end @@ -353,6 +358,7 @@ include("codes/clevecode.jl") include("codes/toric.jl") include("codes/gottesman.jl") include("codes/surface.jl") +include("codes/concat.jl") include("codes/classical/reedmuller.jl") include("codes/classical/bch.jl") end #module diff --git a/src/ecc/codes/concat.jl b/src/ecc/codes/concat.jl new file mode 100644 index 000000000..e0e02f5c8 --- /dev/null +++ b/src/ecc/codes/concat.jl @@ -0,0 +1,47 @@ +""" +`Concat(c₁, c₂)` is a code concatenation of two quantum codes [knill1996concatenated](@cite). + +The inner code c₁ and the outer code c₂. +The construction is the following: replace each qubit in code c₂ with logical qubits encoded by code c₁. +The resulting code will have `n = n₁ × n₂` qubits and `k = k₁ × k₂` logical qubits. +""" +struct Concat <: AbstractECC + c₁::AbstractECC + c₂::AbstractECC +end + +function parity_checks(c::Concat) + c₁ = c.c₁ + c₂ = c.c₂ + k₁ = code_k(c₁) + n₁ = code_n(c₁) + n₂ = code_n(c₂) + s₁ = code_s(c₁) + s₂ = code_s(c₂) + inner_checks = Stabilizer(vcat([embed(n₁ * n₂, 1+(i-1)*n₁:i*n₁, parity_checks(c₁)[j]) for i in 1:n₂ for j in 1:s₁])) # parity checks of c₁ on each qubit of c₂ + h₂ = parity_matrix(c₂) + phases₂ = phases(parity_checks(c₂)) + h_logx₁ = stab_to_gf2(logx_ops(c₁)) + phases_logx₁ = phases(logx_ops(c₁)) + h_logz₁ = stab_to_gf2(logz_ops(c₁)) + phases_logz₁ = phases(logz_ops(c₁)) + # parity checks of c₂ with qubits repalced with logical qubits of c₁ + outer_check_h = transpose(hcat([vcat( + kron(h₂[i, 1:end÷2], h_logx₁[j, 1:end÷2]) .⊻ kron(h₂[i, end÷2+1:end], h_logz₁[j, 1:end÷2]), # X part + kron(h₂[i, 1:end÷2], h_logx₁[j, end÷2+1:end]) .⊻ kron(h₂[i, end÷2+1:end], h_logz₁[j, end÷2+1:end]) # Z part + ) for i in 1:s₂ for j in 1:k₁]...)) + outer_check_phase = [UInt8(sum(h₂[i, 1:end÷2] * phases_logx₁[j]) + sum(h₂[i, end÷2+1:end] * phases_logz₁[j]) + phases₂[i]) & 0x3 for i in 1:s₂ for j in 1:k₁] + outer_checks = Stabilizer(outer_check_phase, outer_check_h) + vcat(inner_checks, outer_checks) +end + +code_n(c::Concat) = code_n(c.c₁) * code_n(c.c₂) + +code_k(c::Concat) = code_k(c.c₁) * code_k(c.c₂) + +function iscss(c::Concat) + if iscss(c.c₁) && iscss(c.c₂) + true + end + return nothing # if c.c₁ or c.c₂ are non-CSS; in this case, `Concat(c₁, c₂)` can still be CSS +end diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index 89b8f5204..ecf9a2999 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -9,7 +9,8 @@ const code_instance_args = Dict( Toric => [(3,3), (4,4), (3,6), (4,3), (5,5)], Surface => [(3,3), (4,4), (3,6), (4,3), (5,5)], Gottesman => [3, 4, 5], - CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4,4)]) + CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4,4)]), + Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2,2), Shor9())], ) function all_testablable_code_instances(;maxn=nothing) diff --git a/test/test_ecc_codeproperties.jl b/test/test_ecc_codeproperties.jl index 90ab54c87..c464ea545 100644 --- a/test/test_ecc_codeproperties.jl +++ b/test/test_ecc_codeproperties.jl @@ -22,7 +22,7 @@ end @testset "is CSS" begin for code in all_testablable_code_instances() H = parity_checks(code) - @test iscss(code) == is_css_matrix(H) + @test iscss(code) in (is_css_matrix(H), nothing) end end From 901b7b2d109facdb697c30deecc426597200a55c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:25:20 -0400 Subject: [PATCH 04/66] Bump dawidd6/action-download-artifact from 5 to 6 (#292) Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 5 to 6. - [Release notes](https://github.com/dawidd6/action-download-artifact/releases) - [Commits](https://github.com/dawidd6/action-download-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: dawidd6/action-download-artifact 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/benchmark-comment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmark-comment.yml b/.github/workflows/benchmark-comment.yml index 247d792e5..fc39cc293 100644 --- a/.github/workflows/benchmark-comment.yml +++ b/.github/workflows/benchmark-comment.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v4 # restore records from the artifacts - - uses: dawidd6/action-download-artifact@v5 + - uses: dawidd6/action-download-artifact@v6 with: workflow: benchmark.yml name: performance-tracking From f897336b61c56654998b36cfb0966b71b5e37158 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Wed, 19 Jun 2024 02:13:29 -0400 Subject: [PATCH 05/66] warning on failed compactification --- CHANGELOG.md | 3 ++- src/pauli_frames.jl | 7 ++++++- test/runtests.jl | 1 + test/test_sumtypecompactification.jl | 7 +++++++ 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 test/test_sumtypecompactification.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index c6d5cc47c..d53e8db88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,11 @@ # News -## v0.9.4 - 2024-06-14 +## v0.9.4 - 2024-06-19 - Addition of a constructor for concatenated quantum codes -- `Concat`. - Addition of multiple unexported classical code constructors. +- Failed compactification of gates now only raises a warning instead of throwing an error. Defaults to slower non-compactified gates. - Gate errors are now conveniently supported by the various ECC benchmark setups in the `ECC` module. - Remove printing of spurious debug info from the PyBP decoder. - Significant improvements to the low-level circuit compiler (the sumtype compactifier), leading to faster Pauli frame simulation of noisy circuits. diff --git a/src/pauli_frames.jl b/src/pauli_frames.jl index 0fde80f1c..2a4504c8a 100644 --- a/src/pauli_frames.jl +++ b/src/pauli_frames.jl @@ -175,7 +175,12 @@ function _pftrajectories(circuit;trajectories=5000,threads=true) ccircuit = if eltype(circuit) <: CompactifiedGate circuit else - compactify_circuit(circuit) + try + compactify_circuit(circuit) + catch err + @warn "Could not compactify the circuit, falling back to a slower version of the simulation. Consider reporting this issue to the package maintainers to improve performance. The offending gate was `$(err.args[2])`." + circuit + end end frames = _create_pauliframe(ccircuit; trajectories) nthr = min(Threads.nthreads(),trajectories÷(100)) diff --git a/test/runtests.jl b/test/runtests.jl index 1177d673f..f274479a4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -69,6 +69,7 @@ end @doset "ecc_throws" @doset "precompile" @doset "pauliframe" +@doset "sumtypecompactification" @doset "allocations" VERSION >= v"1.10" && @doset "doctests" get(ENV,"JET_TEST","")=="true" && @doset "jet" diff --git a/test/test_sumtypecompactification.jl b/test/test_sumtypecompactification.jl new file mode 100644 index 000000000..11c902d4e --- /dev/null +++ b/test/test_sumtypecompactification.jl @@ -0,0 +1,7 @@ +using Test +using QuantumClifford + +@testset "SumTypes compactification" begin + @test_warn "Could not compactify the circuit" QuantumClifford.pftrajectories([ClassicalXOR{17}((65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81), 282)]) + QuantumClifford.compactify_circuit([ClassicalXOR{3}((65, 66, 67), 282)]) +end From ca36dcce0c830206b78d40b4bcbdd93d747fce1f Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Wed, 19 Jun 2024 02:27:45 -0400 Subject: [PATCH 06/66] fix unrelated downgrading issue to bad PyQDecoders version (due to the breaking release of numpy 2) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 15c21efa5..efbf60078 100644 --- a/Project.toml +++ b/Project.toml @@ -56,7 +56,7 @@ Makie = "0.20, 0.21" Nemo = "0.42, 0.43, 0.44, 0.45" Plots = "1.38.0" PrecompileTools = "1.2" -PyQDecoders = "0.2.0" +PyQDecoders = "0.2.1" Quantikz = "1.3.1" QuantumInterface = "0.3.3" QuantumOpticsBase = "0.4.18" From a55f1f2c4832ac316a521d6fcee83d798cf5858c Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Wed, 19 Jun 2024 11:29:27 -0400 Subject: [PATCH 07/66] cleanup --- .github/workflows/benchmark.yml | 2 +- test/test_jet.jl | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 34c65be0d..2e0c0b3f8 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: - version: 'nightly' + version: '1.10' - uses: julia-actions/julia-buildpkg@latest - name: install dependencies run: julia -e 'using Pkg; pkg"add PkgBenchmark BenchmarkCI@0.1"' diff --git a/test/test_jet.jl b/test/test_jet.jl index 131ffa8eb..8b4f0c330 100644 --- a/test/test_jet.jl +++ b/test/test_jet.jl @@ -1,3 +1,4 @@ +using Test using QuantumClifford using JET using ArrayInterface @@ -36,5 +37,5 @@ end ) @show rep @test_broken length(JET.get_reports(rep)) == 0 - @test length(JET.get_reports(rep)) <= 25 + @test length(JET.get_reports(rep)) <= 28 end From ee430b824857c9cb6711dae87301d18e8d453524 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Wed, 19 Jun 2024 12:45:20 -0400 Subject: [PATCH 08/66] new JET badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2556d3e1e..f64fd7ce5 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Static analysis with - JET static analysis + JET static analysis Aqua QA From ad2d697cdd7cd96135b8e606cd8a54c800d3d429 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Wed, 19 Jun 2024 20:10:39 -0400 Subject: [PATCH 09/66] use juliaup for nightly tests --- .github/workflows/ci-julia-nightly.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-julia-nightly.yml b/.github/workflows/ci-julia-nightly.yml index dd794abd0..94e3854a6 100644 --- a/.github/workflows/ci-julia-nightly.yml +++ b/.github/workflows/ci-julia-nightly.yml @@ -16,7 +16,7 @@ jobs: include: - os: ubuntu-latest arch: x64 - version: nightly + version: alpha threads: 2 jet: 'false' - os: ubuntu-latest @@ -26,10 +26,9 @@ jobs: jet: 'true' steps: - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@v1 + - uses: julia-actions/install-juliaup@v2 with: - version: ${{ matrix.version }} - arch: ${{ matrix.arch }} + channel: ${{ matrix.version }}~${{ matrix.arch }} - uses: julia-actions/cache@v2 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 From aa70f69c1326af48463a641625b7b50c75b4a7dd Mon Sep 17 00:00:00 2001 From: CompatHelper Julia Date: Thu, 27 Jun 2024 01:03:07 +0000 Subject: [PATCH 10/66] CompatHelper: bump compat for QuantumOpticsBase in [weakdeps] to 0.5, (keep existing compat) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index efbf60078..7e351585a 100644 --- a/Project.toml +++ b/Project.toml @@ -59,7 +59,7 @@ PrecompileTools = "1.2" PyQDecoders = "0.2.1" Quantikz = "1.3.1" QuantumInterface = "0.3.3" -QuantumOpticsBase = "0.4.18" +QuantumOpticsBase = "0.4.18, 0.5" Random = "1.9" SIMD = "3.4.0" SparseArrays = "1.9" From 53002d58a82740839557c2e7c4027aaa6adcd507 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Fri, 28 Jun 2024 17:19:07 -0400 Subject: [PATCH 11/66] bump --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d53e8db88..3ddde011d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ # News -## v0.9.4 - 2024-06-19 +## v0.9.4 - 2024-06-28 - Addition of a constructor for concatenated quantum codes -- `Concat`. - Addition of multiple unexported classical code constructors. @@ -13,6 +13,7 @@ - Gate errors are now conveniently supported by the various ECC benchmark setups in the `ECC` module. - Remove printing of spurious debug info from the PyBP decoder. - Significant improvements to the low-level circuit compiler (the sumtype compactifier), leading to faster Pauli frame simulation of noisy circuits. +- Bump `QuantumOpticsBase.jl` package extension compat bound. ## v0.9.3 - 2024-04-10 From 59e399d17ddb1430482ed343d221680754b54bf0 Mon Sep 17 00:00:00 2001 From: Rabqubit Date: Fri, 5 Jul 2024 07:51:29 +0800 Subject: [PATCH 12/66] add random Clifford circuit codes (#298) --------- Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 10 +++-- Project.toml | 2 +- docs/src/references.bib | 26 ++++++++--- docs/src/tutandpub.md | 2 +- src/QuantumClifford.jl | 1 + src/ecc/ECC.jl | 4 +- src/ecc/codes/random_circuit.jl | 79 +++++++++++++++++++++++++++++++++ src/entanglement.jl | 4 +- src/randoms.jl | 57 ++++++++++++++++++++++++ test/test_ecc_base.jl | 14 +++++- 10 files changed, 184 insertions(+), 15 deletions(-) create mode 100644 src/ecc/codes/random_circuit.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ddde011d..18c7f5a8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,15 +5,19 @@ # News +## v0.9.5 - 2024-07-04 + +- Implementation of random all-to-all and brickwork Clifford circuits and corresponding ECC codes. + ## v0.9.4 - 2024-06-28 -- Addition of a constructor for concatenated quantum codes -- `Concat`. +- Addition of a constructor for concatenated quantum codes `Concat`. - Addition of multiple unexported classical code constructors. -- Failed compactification of gates now only raises a warning instead of throwing an error. Defaults to slower non-compactified gates. - Gate errors are now conveniently supported by the various ECC benchmark setups in the `ECC` module. -- Remove printing of spurious debug info from the PyBP decoder. - Significant improvements to the low-level circuit compiler (the sumtype compactifier), leading to faster Pauli frame simulation of noisy circuits. - Bump `QuantumOpticsBase.jl` package extension compat bound. +- **(fix)** Remove printing of spurious debug info from the PyBP decoder. +- **(fix)** Failed compactification of gates now only raises a warning instead of throwing an error. Defaults to slower non-compactified gates. ## v0.9.3 - 2024-04-10 diff --git a/Project.toml b/Project.toml index 7e351585a..92a6bbb5d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.4" +version = "0.9.5" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/docs/src/references.bib b/docs/src/references.bib index c492d0ea7..6a50a9194 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -165,11 +165,17 @@ @article{grassl2002algorithmic % Examples of results that employ the tableaux formalism -@article{gullans2020quantum, - title={Quantum coding with low-depth random circuits}, - author={Gullans, Michael J and Krastanov, Stefan and Huse, David A and Jiang, Liang and Flammia, Steven T}, - journal={arXiv preprint arXiv:2010.09775}, - year={2020} +@article{gullans2021quantum, + title = {Quantum {{Coding}} with {{Low-Depth Random Circuits}}}, + author = {Gullans, Michael J. and Krastanov, Stefan and Huse, David A. and Jiang, Liang and Flammia, Steven T.}, + year = {2021}, + month = sep, + journal = {Physical Review X}, + volume = {11}, + number = {3}, + pages = {031066}, + issn = {2160-3308}, + doi = {10.1103/PhysRevX.11.031066} } @article{krastanov2020heterogeneous, @@ -386,3 +392,13 @@ @article{knill1996concatenated journal={arXiv preprint quant-ph/9608012}, year={1996} } + +@inproceedings{brown2013short, + title = {Short Random Circuits Define Good Quantum Error Correcting Codes}, + booktitle = {2013 {{IEEE International Symposium}} on {{Information Theory}}}, + author = {Brown, Winton and Fawzi, Omar}, + year = {2013}, + month = jul, + pages = {346--350}, + doi = {10.1109/ISIT.2013.6620245} +} diff --git a/docs/src/tutandpub.md b/docs/src/tutandpub.md index e64f16321..d6b73ca35 100644 --- a/docs/src/tutandpub.md +++ b/docs/src/tutandpub.md @@ -4,7 +4,7 @@ This list has a number of notebooks with tutorials, examples, and reproduction o ## On the topic of explicit use of the Tableaux formalism for Stabilizer states -- [Quantum coding with low-depth random circuits](https://github.com/QuantumSavory/QuantumClifford.jl/blob/master/docs/src/notebooks/Stabilizer_Codes_Based_on_Random_Circuits.ipynb) reproducing results from [gullans2020quantum](@cite). [view on nbviewer.jupyter.org](https://nbviewer.jupyter.org/github/QuantumSavory/QuantumClifford.jl/blob/master/docs/src/notebooks/Stabilizer_Codes_Based_on_Random_Circuits.ipynb) +- [Quantum coding with low-depth random circuits](https://github.com/QuantumSavory/QuantumClifford.jl/blob/master/docs/src/notebooks/Stabilizer_Codes_Based_on_Random_Circuits.ipynb) reproducing results from [gullans2021quantum](@cite). [view on nbviewer.jupyter.org](https://nbviewer.jupyter.org/github/QuantumSavory/QuantumClifford.jl/blob/master/docs/src/notebooks/Stabilizer_Codes_Based_on_Random_Circuits.ipynb) ## On the Monte Carlo and Perturbative Expansions for **Noisy** Clifford circuits diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 458a10ff5..44fb23312 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -63,6 +63,7 @@ export random_invertible_gf2, random_pauli, random_pauli!, random_stabilizer, random_destabilizer, random_clifford, + random_brickwork_clifford_circuit, random_all_to_all_clifford_circuit, # Noise applynoise!, UnbiasedUncorrelatedNoise, NoiseOp, NoiseOpAll, NoisyGate, PauliNoise, PauliError, diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index bae3df389..ed50dba5b 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -19,7 +19,8 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, RepCode, CSS, Shor9, Steane7, Cleve8, Perfect5, Bitflip3, - Toric, Gottesman, Surface, Concat, + Toric, Gottesman, Surface, Concat, CircuitCode, + random_brickwork_circuit_code, random_all_to_all_circuit_code, evaluate_decoder, CommutationCheckECCSetup, NaiveSyndromeECCSetup, ShorSyndromeECCSetup, TableDecoder, @@ -359,6 +360,7 @@ include("codes/toric.jl") include("codes/gottesman.jl") include("codes/surface.jl") include("codes/concat.jl") +include("codes/random_circuit.jl") include("codes/classical/reedmuller.jl") include("codes/classical/bch.jl") end #module diff --git a/src/ecc/codes/random_circuit.jl b/src/ecc/codes/random_circuit.jl new file mode 100644 index 000000000..d44e0e62c --- /dev/null +++ b/src/ecc/codes/random_circuit.jl @@ -0,0 +1,79 @@ +using Random: AbstractRNG, GLOBAL_RNG + + +""" +`CircuitCode` is defined by a given encoding circuit `circ`. + +- `n`: qubit number +- `circ`: the encoding circuit +- `encode_qubits`: the qubits to be encoded + +See also: [`random_all_to_all_circuit_code`](@ref), [`random_brickwork_circuit_code`](@ref) +""" +struct CircuitCode <: AbstractECC + n::Int + circ::Vector{QuantumClifford.AbstractOperation} + encode_qubits::AbstractArray +end + +iscss(::Type{CircuitCode}) = nothing + +code_n(c::CircuitCode) = c.n + +code_k(c::CircuitCode) = length(c.encode_qubits) + +function parity_checks(c::CircuitCode) + n = code_n(c) + checks = one(Stabilizer, n)[setdiff(1:n, c.encode_qubits)] + for op in c.circ + apply!(checks, op) + end + checks +end + +""" +Random all-to-all Clifford circuit code [brown2013short](@cite). + +The code of `n` qubits is generated by an all-to-all random Clifford circuit of `ngates` gates that encodes a subset of qubits `encode_qubits` into logical qubits. + +Because of the random picking, the size of `encode_qubits` is the only thing that matters for the code, referred to as `k`. + +See also: [`random_all_to_all_clifford_circuit`](@ref), [`CircuitCode`](@ref) +""" +function random_all_to_all_circuit_code end + +function random_all_to_all_circuit_code(rng::AbstractRNG, n::Int, ngates::Int, k::Int) + CircuitCode(n, random_all_to_all_clifford_circuit(rng, n, ngates), collect(1:k)) +end + +function random_all_to_all_circuit_code(n::Int, ngates::Int, k::Int) + CircuitCode(n, random_all_to_all_clifford_circuit(n, ngates), collect(1:k)) +end + +function random_all_to_all_circuit_code(rng::AbstractRNG, n::Int, ngates::Int, encode_qubits::AbstractArray) + CircuitCode(n, random_all_to_all_clifford_circuit(rng, n, ngates), encode_qubits) +end + +function random_all_to_all_circuit_code(n::Int, ngates::Int, encode_qubits::AbstractArray) + CircuitCode(n, random_all_to_all_clifford_circuit(n, ngates), encode_qubits) +end + + +""" +Random brickwork Clifford circuit code [brown2013short](@cite). + +The code is generated by a brickwork random Clifford circuit of `nlayers` layers that encodes a subset of qubits `encode_qubits` into logical qubits. + +See also: [`random_brickwork_clifford_circuit`](@ref), [`CircuitCode`](@ref) +""" +function random_brickwork_circuit_code end + +# TODO it would be nicer if we can use CartesianIndex for `encode_qubits` in brickworks, +# but its conversion to LinearIndex is limited, not supporting non-one step. +function random_brickwork_circuit_code(rng::AbstractRNG, lattice_size::NTuple{N,Int} where {N}, nlayers::Int, encode_qubits::AbstractArray) + CircuitCode(prod(lattice_size), random_brickwork_clifford_circuit(rng, lattice_size, nlayers), encode_qubits) +end + +function random_brickwork_circuit_code(lattice_size::NTuple{N,Int} where {N}, nlayers::Int, encode_qubits::AbstractArray) + CircuitCode(prod(lattice_size), random_brickwork_clifford_circuit(lattice_size, nlayers), encode_qubits) +end diff --git a/src/entanglement.jl b/src/entanglement.jl index 31e7c3e8c..f4fd12b71 100644 --- a/src/entanglement.jl +++ b/src/entanglement.jl @@ -139,7 +139,7 @@ It is the list of endpoints of a tableau in the clipped gauge. If `clip=true` (the default) the tableau is converted to the clipped gauge in-place before calculating the bigram. Otherwise, the clip gauge conversion is skipped (for cases where the input is already known to be in the correct gauge). -Introduced in [nahum2017quantum](@cite), with a more detailed explanation of the algorithm in [li2019measurement](@cite) and [gullans2020quantum](@cite). +Introduced in [nahum2017quantum](@cite), with a more detailed explanation of the algorithm in [li2019measurement](@cite) and [gullans2021quantum](@cite). See also: [`canonicalize_clip!`](@ref) """ @@ -186,7 +186,7 @@ function entanglement_entropy(state::AbstractStabilizer, subsystem_range::UnitRa # JET-XXX The ::Matrix{Int} should not be necessary, but they help with inference bg = bigram(state; clip=clip)::Matrix{Int} # If the state is mixed, this formula is valid only for contiguous regions that don't wrap around. - # See Eq. E7 of gullans2020quantum. + # See Eq. E7 of gullans2021quantum. # As subsystem_range is UnitRange, we know the formula will be valid. length(subsystem_range) - count(r->(r[1] in subsystem_range && r[2] in subsystem_range), eachrow(bg)) end diff --git a/src/randoms.jl b/src/randoms.jl index 5378fbef2..1d4fff388 100644 --- a/src/randoms.jl +++ b/src/randoms.jl @@ -228,3 +228,60 @@ function fill_tril(rng, matrix, n; symmetric::Bool=false) end matrix end + +############################## +# Random circuit +############################## + +""" +Random brickwork Clifford circuit. + +The connectivity of the random circuit is brickwork in some dimensions. Each gate in the circuit is a random 2-qubit Clifford gate. + +The brickwork is defined as follows: The qubits are arranged as a lattice, and `lattice_size` contains side length in each dimension. +For example, a chain of length five will have `lattice_size = (5,)`, and a 5×5 lattice will have `lattice_size = (5, 5)`. + +In multi-dimensional cases, gate layers act alternatively along each direction. +The nearest two layers along the same direction are offset by one qubit, forming a so-called brickwork. +The boundary condition is chosen as open. +""" +function random_brickwork_clifford_circuit(rng::AbstractRNG, lattice_size::NTuple{N,Int} where {N}, nlayers::Int) + circ = QuantumClifford.SparseGate[] + cartesian = CartesianIndices(lattice_size) + dim = length(lattice_size) + nqubits = prod(lattice_size) + for i in 1:nlayers + gate_direction = (i - 1) % dim + 1 + l = lattice_size[gate_direction] + brickwise_parity = dim == 1 ? i % 2 : 1 - (i ÷ dim) % 2 + for j in 1:nqubits + cardj = collect(cartesian[j].I) + if cardj[gate_direction] % 2 == brickwise_parity && cardj[gate_direction] != l # open boundary + cardk = cardj + cardk[gate_direction] = cardk[gate_direction] + 1 + k = LinearIndices(cartesian)[cardk...] + push!(circ, SparseGate(random_clifford(rng, 2), [j, k])) + end + end + end + circ +end + +random_brickwork_clifford_circuit(lattice_size::NTuple{N,Int} where {N}, nlayers::Int) = random_brickwork_clifford_circuit(GLOBAL_RNG, lattice_size, nlayers) + +""" +Random all-to-all Clifford circuit. + +The circuit contains `nqubits` qubits and `ngates` gates. The connectivity is all to all. Each gate in the circuit is a random 2-qubit Clifford gate on randomly picked two qubits. +""" +function random_all_to_all_clifford_circuit(rng::AbstractRNG, nqubits::Int, ngates::Int) + circ = QuantumClifford.SparseGate[] + for i in 1:ngates + j = rand(1:nqubits) + k = rand(1:nqubits-1) + push!(circ, SparseGate(random_clifford(rng, 2), [j, (j + k - 1) % nqubits + 1])) + end + circ +end + +random_all_to_all_clifford_circuit(nqubits::Int, ngates::Int) = random_all_to_all_clifford_circuit(GLOBAL_RNG, nqubits, ngates) diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index ecf9a2999..9b4dac227 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -5,12 +5,22 @@ using InteractiveUtils # generate instances of all implemented codes to make sure nothing skips being checked +# We do not include smaller random circuit code because some of them has a bad distance and fails the TableDecoder test +const random_brickwork_circuit_args = repeat([((20,), 50, [1]), ((20,), 50, 1:2:20), ((5, 5), 50, [1]), ((3, 3, 3), 50, [1])], 10) +const random_all_to_all_circuit_args = repeat([(20, 200, 1), (40, 200, [1, 20])], 10) + +random_circuit_code_args = vcat( + [map(f -> getfield(random_brickwork_circuit_code(c...), f), fieldnames(CircuitCode)) for c in random_brickwork_circuit_args], + [map(f -> getfield(random_all_to_all_circuit_code(c...), f), fieldnames(CircuitCode)) for c in random_all_to_all_circuit_args] +) + const code_instance_args = Dict( Toric => [(3,3), (4,4), (3,6), (4,3), (5,5)], Surface => [(3,3), (4,4), (3,6), (4,3), (5,5)], Gottesman => [3, 4, 5], - CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4,4)]), - Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2,2), Shor9())], + CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4, 4)]), + Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2, 2), Shor9())], + CircuitCode => random_circuit_code_args ) function all_testablable_code_instances(;maxn=nothing) From 10bb4b6cef05341caad61162b518735ae82fe3fd Mon Sep 17 00:00:00 2001 From: Feroz Ahmad <93876775+Fe-r-oz@users.noreply.github.com> Date: Thu, 18 Jul 2024 01:00:39 +0500 Subject: [PATCH 13/66] `inv` implementation for `SingleQubitOperator` (#314) --------- Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- src/symbolic_cliffords.jl | 13 +++++++++++++ test/test_symcliff.jl | 14 +++++++++++++- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18c7f5a8b..ec4e15c24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ # News +## v0.9.6 - 2024-07-12 + +- `inv` implementation for single-qubit "symbolic" Clifford operators (subtypes of `AbstractSingleQubitOperator`). + ## v0.9.5 - 2024-07-04 - Implementation of random all-to-all and brickwork Clifford circuits and corresponding ECC codes. diff --git a/Project.toml b/Project.toml index 92a6bbb5d..2f3303f4b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.5" +version = "0.9.6" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index b143589b3..0d2c26c40 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -227,6 +227,19 @@ function random_clifford1(rng::AbstractRNG, qubit) end random_clifford1(qubit) = random_clifford1(GLOBAL_RNG, qubit) +function LinearAlgebra.inv(op::SingleQubitOperator) + c = LinearAlgebra.inv(CliffordOperator(SingleQubitOperator(op), 1, compact=true)) + return SingleQubitOperator(c, op.q) +end + +LinearAlgebra.inv(h::sHadamard) = sHadamard(h.q) +LinearAlgebra.inv(p::sPhase) = sInvPhase(p.q) +LinearAlgebra.inv(p::sInvPhase) = sPhase(p.q) +LinearAlgebra.inv(p::sId1) = sId1(p.q) +LinearAlgebra.inv(p::sX) = sX(p.q) +LinearAlgebra.inv(p::sY) = sY(p.q) +LinearAlgebra.inv(p::sZ) = sZ(p.q) + ############################## # Two-qubit gates ############################## diff --git a/test/test_symcliff.jl b/test/test_symcliff.jl index 9cfddb976..2af1fd538 100644 --- a/test/test_symcliff.jl +++ b/test/test_symcliff.jl @@ -1,6 +1,5 @@ using Random using QuantumClifford - using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good using QuantumClifford: apply_single_x!, apply_single_y!, apply_single_z! using InteractiveUtils @@ -66,3 +65,16 @@ end @test op1 == op2 end end + +@testset "SingleQubitOperator inv methods" begin + for gate_type in [sHadamard, sX, sY, sZ, sId1 , sPhase, sInvPhase] + n = rand(1:10) + @test CliffordOperator(inv(SingleQubitOperator(gate_type(n))), n) == inv(CliffordOperator(gate_type(n), n)) + @test CliffordOperator(inv(gate_type(n)), n) == inv(CliffordOperator(gate_type(n), n)) + end + for i in 1:10 + random_op = random_clifford1(i) + @test CliffordOperator(inv(random_op), i) == inv(CliffordOperator(random_op, i)) + @test CliffordOperator(inv(SingleQubitOperator(random_op)), i) == inv(CliffordOperator(random_op, i)) + end +end From 3ea541405c78dd315332579e8ecdd2e4b1befd46 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 20 Jul 2024 13:47:06 -0400 Subject: [PATCH 14/66] CompatHelper: bump compat for Nemo to 0.46, (keep existing compat) (#318) Co-authored-by: CompatHelper Julia --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 2f3303f4b..a2ca448b8 100644 --- a/Project.toml +++ b/Project.toml @@ -53,7 +53,7 @@ LDPCDecoders = "0.3.1" LinearAlgebra = "1.9" MacroTools = "0.5.9" Makie = "0.20, 0.21" -Nemo = "0.42, 0.43, 0.44, 0.45" +Nemo = "0.42, 0.43, 0.44, 0.45, 0.46" Plots = "1.38.0" PrecompileTools = "1.2" PyQDecoders = "0.2.1" From 1fb178385c2a343d3cf7148505c27bd88f7990c3 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Tue, 23 Jul 2024 13:07:30 -0400 Subject: [PATCH 15/66] fix for wrong SIMD mul_left! #320 (#322) --- CHANGELOG.md | 3 +++ Project.toml | 2 +- src/mul_leftright.jl | 2 +- test/test_mul_leftright.jl | 17 ++++++++++++++++- test/test_stabcanon.jl | 13 +++++++++++++ 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec4e15c24..1c8952376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ # News +## v0.9.7 - 2024-07-23 + +- **(fix `#320`)** Fix a serious correctness bug in the SIMD implementation of Pauli string multiplication (affects the correctness of canonicalization and traceout for tableaux bigger than ~500 qubits; does not affect symbolic gates or Pauli frame simulations of any scale) ## v0.9.6 - 2024-07-12 - `inv` implementation for single-qubit "symbolic" Clifford operators (subtypes of `AbstractSingleQubitOperator`). diff --git a/Project.toml b/Project.toml index a2ca448b8..196ddf832 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.6" +version = "0.9.7" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/src/mul_leftright.jl b/src/mul_leftright.jl index 43a3731cd..122fb1e61 100644 --- a/src/mul_leftright.jl +++ b/src/mul_leftright.jl @@ -89,7 +89,7 @@ function mul_ordered!(r::AbstractVector{T}, l::AbstractVector{T}; phases::Val{B} r[i+len+lane] = newz1 = z1 ⊻ z2 x1z2 = x1 & z2 anti_comm = (x2 & z1) ⊻ x1z2 - cnt2 ⊻= (newx1 ⊻ newz1 ⊻ x1z2) & anti_comm + cnt2 ⊻= (cnt1 ⊻ newx1 ⊻ newz1 ⊻ x1z2) & anti_comm cnt1 ⊻= anti_comm end for i in 1:length(cnt1) diff --git a/test/test_mul_leftright.jl b/test/test_mul_leftright.jl index ecf2b36eb..1d7c14e45 100644 --- a/test/test_mul_leftright.jl +++ b/test/test_mul_leftright.jl @@ -5,7 +5,7 @@ using Test test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -@testset "Inner product between stabilizer states" begin +@testset "Pauli string multiplication" begin for n in test_sizes for _ in 1:20 p1 = random_pauli(n) @@ -22,3 +22,18 @@ test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off end end end + +# test for #320 +@testset "verify SIMD implementation" begin + for i in 1:10, + n in 1:30, + T in [UInt8, UInt16, UInt32, UInt64] + a = rand(T, n) + b = rand(T, n) + c1,c2 = QuantumClifford.mul_ordered!(copy(a),copy(b)) + n1,n2 = QuantumClifford._mul_ordered_nonvec!(copy(a),copy(b)) + np = ((n1 ⊻ (n2<<1))&0x3) + cp = ((c1 ⊻ (c2<<1))&0x3) + @test np==cp + end +end diff --git a/test/test_stabcanon.jl b/test/test_stabcanon.jl index 13442e4eb..5c113c69b 100644 --- a/test/test_stabcanon.jl +++ b/test/test_stabcanon.jl @@ -74,3 +74,16 @@ test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off end end end + +@testset "canonicalization invariants" begin + s = random_stabilizer(40,100) + ss = tensor_pow(s,20) + sa1 = canonicalize!(canonicalize_rref!(copy(ss))[1]) + sa2 = canonicalize!(copy(ss)) + @test sa1 == sa2 + ms = MixedDestabilizer(s) + mss = tensor_pow(ms, 20) + msa1 = canonicalize!(canonicalize_rref!(copy(mss))[1]) + msa2 = canonicalize!(copy(mss)) + @test stabilizerview(msa1) == stabilizerview(msa2) == sa1 +end From 314c0bf88f814c011013e7a84bcb401c7d8af7aa Mon Sep 17 00:00:00 2001 From: Feroz Ahmad <93876775+Fe-r-oz@users.noreply.github.com> Date: Tue, 30 Jul 2024 08:00:23 +0500 Subject: [PATCH 16/66] introducing `doctests` for `tensor` methods and `inv` (#326) --------- Co-authored-by: Stefan Krastanov --- src/linalg.jl | 122 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-) diff --git a/src/linalg.jl b/src/linalg.jl index d54881105..16b4eaf52 100644 --- a/src/linalg.jl +++ b/src/linalg.jl @@ -2,6 +2,24 @@ $TYPEDSIGNATURES Inverse of a `CliffordOperator` + +```jldoctest +julia> inv(CliffordOperator(sCNOT)) +X₁ ⟼ + XX +X₂ ⟼ + _X +Z₁ ⟼ + Z_ +Z₂ ⟼ + ZZ + +julia> inv(CliffordOperator(sCNOT(2, 1), 2)) +X₁ ⟼ + X_ +X₂ ⟼ + XX +Z₁ ⟼ + ZZ +Z₂ ⟼ + _Z + +julia> inv(CliffordOperator(tHadamard)) +X₁ ⟼ + Z +Z₁ ⟼ + X +``` """ function LinearAlgebra.inv(c::CliffordOperator; phases=true) ci = zero(c) @@ -24,6 +42,16 @@ end Based on [garcia2012efficient](@cite). +```jldoctest +julia> using LinearAlgebra + +julia> dot(S"Z", S"Z") +1.0 + +julia> dot(S"Z", S"Y") +0.7071067811865476 +``` + See also: [`logdot`](@ref)""" function LinearAlgebra.dot(s1::AbstractStabilizer, s2::AbstractStabilizer) ld = logdot(s1,s2) @@ -83,14 +111,104 @@ trusted_rank(s::Destabilizer) = length(s) trusted_rank(s::MixedStabilizer) = LinearAlgebra.rank(s) trusted_rank(s::MixedDestabilizer) = LinearAlgebra.rank(s) -"""Tensor product between operators or tableaux. See also [`tensor_pow`](@ref).""" +"""Tensor product between operators or tableaux. + +Tensor product between CiffordOperators: + +```jldoctest +julia> tensor(CliffordOperator(sCNOT), CliffordOperator(sCNOT)) +X₁ ⟼ + XX__ +X₂ ⟼ + _X__ +X₃ ⟼ + __XX +X₄ ⟼ + ___X +Z₁ ⟼ + Z___ +Z₂ ⟼ + ZZ__ +Z₃ ⟼ + __Z_ +Z₄ ⟼ + __ZZ +``` + +Tensor product between PauliOperators: + +```jldoctest +julia> tensor(P"-IXYZ", P"iIXYZ") +-i_XYZ_XYZ +``` + +Tensor product between Tableaux: + +```jldoctest +julia> s = S"-XX + +ZZ"; + +julia> tensor(s, s, s) +- XX____ ++ ZZ____ +- __XX__ ++ __ZZ__ +- ____XX ++ ____ZZ + +julia> s = S"+XZI + -IZI"; + +julia> tensor(s, s) ++ XZ____ +- _Z____ ++ ___XZ_ +- ____Z_ +``` + +See also [`tensor_pow`](@ref).""" function tensor end function tensor(ops::AbstractStabilizer...) # TODO optimize this by doing conversion to common type to enable preallocation foldl(⊗, ops[2:end], init=ops[1]) end -"""Repeated tensor product of an operators or a tableau. See also [`tensor`](@ref).""" +"""Repeated tensor product of an operators or a tableau. + +For `CliffordOperator`: + +```jldoctest +julia> tensor_pow(CliffordOperator(sHadamard), 3) +X₁ ⟼ + Z__ +X₂ ⟼ + _Z_ +X₃ ⟼ + __Z +Z₁ ⟼ + X__ +Z₂ ⟼ + _X_ +Z₃ ⟼ + __X +``` + +For `PauliOperator`: + +```jldoctest +julia> tensor_pow(P"IXYZ", 2) ++ _XYZ_XYZ +``` + +For `Tableaux`: + +```jldoctest +julia> tensor_pow(S"Z", 4) ++ Z___ ++ _Z__ ++ __Z_ ++ ___Z + +julia> s = S"+XZI + +IZI"; + +julia> tensor_pow(s, 3) ++ XZ_______ ++ _Z_______ ++ ___XZ____ ++ ____Z____ ++ ______XZ_ ++ _______Z_ +``` + +See also [`tensor`](@ref). +""" function tensor_pow(op::Union{<:AbstractStabilizer,<:AbstractCliffordOperator},power) if power==1 return op From f5b8b4784d4ed2dbfdda3790c42a586bb4209c0a Mon Sep 17 00:00:00 2001 From: Anthony Micciche <32377705+amicciche@users.noreply.github.com> Date: Mon, 29 Jul 2024 23:02:39 -0400 Subject: [PATCH 17/66] beginning of biased pauli noise for PF sim (#295) Co-authored-by: Stefan Krastanov Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 5 +++++ src/noise.jl | 42 +++++++++++++++++++++++++++++++++++++++++- src/pauli_frames.jl | 22 ++++++++++++++++++++++ src/sumtypes.jl | 5 ++++- 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c8952376..a150277a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,14 @@ # News +## v0.9.8 - 2024-07-24 + +- `PauliError` can now encode biased noise during Pauli frame simulation, i.e. one can simulate only X errors, or only Y errors, or only Z errors, or some weighted combination of these. + ## v0.9.7 - 2024-07-23 - **(fix `#320`)** Fix a serious correctness bug in the SIMD implementation of Pauli string multiplication (affects the correctness of canonicalization and traceout for tableaux bigger than ~500 qubits; does not affect symbolic gates or Pauli frame simulations of any scale) + ## v0.9.6 - 2024-07-12 - `inv` implementation for single-qubit "symbolic" Clifford operators (subtypes of `AbstractSingleQubitOperator`). diff --git a/src/noise.jl b/src/noise.jl index c4e7b095a..8398e032e 100644 --- a/src/noise.jl +++ b/src/noise.jl @@ -28,6 +28,19 @@ struct UnbiasedUncorrelatedNoise{T} <: AbstractNoise end UnbiasedUncorrelatedNoise(p::Integer) = UnbiasedUncorrelatedNoise(float(p)) +"""Pauli noise model with probabilities `px`, `py`, and `pz` respectively for the three types of Pauli errors.""" +struct PauliNoise{T} <: AbstractNoise + px::T + py::T + pz::T +end +function PauliNoise(px::Real, py::Real, pz::Real) + px, py, pz = float.((px, py, pz)) + px, py, pz = promote(px, py, pz) + T = typeof(px) + return PauliNoise{T}(px, py, pz) +end + """A convenient constructor for various types of Pauli noise models. Returns more specific types when necessary.""" function PauliNoise end @@ -38,7 +51,6 @@ function PauliNoise(p) end function applynoise!(s::AbstractStabilizer,noise::UnbiasedUncorrelatedNoise,i::Int) - n = nqubits(s) infid = noise.p/3 r = rand() if r Date: Sat, 3 Aug 2024 19:54:47 +0200 Subject: [PATCH 18/66] Move the test runner to use TestItems.jl (#329) Co-authored-by: Stefan Krastanov --- test/Project.toml | 2 +- test/runtests.jl | 88 +---- test/test_allocations.jl | 6 +- test/test_aqua.jl | 7 +- test/test_bitpack.jl | 81 +++-- test/test_classicalreg.jl | 48 +-- test/test_cliff.jl | 182 +++++----- test/test_doctests.jl | 19 +- test/test_ecc.jl | 118 +++--- test/test_ecc_bch.jl | 150 ++++---- test/test_ecc_codeproperties.jl | 62 ++-- test/test_ecc_decoder_all_setups.jl | 170 ++++----- test/test_ecc_encoding.jl | 10 +- test/test_ecc_gottesman.jl | 11 +- test/test_ecc_reedmuller.jl | 122 +++---- test/test_ecc_syndromes.jl | 80 ++-- test/test_ecc_throws.jl | 14 +- test/test_embed.jl | 5 +- test/test_entanglement.jl | 75 ++-- test/test_enumerate.jl | 4 +- test/test_expect.jl | 50 ++- test/test_gf2.jl | 47 +-- test/test_gpu.jl | 238 ++++++------ test/test_graphs.jl | 9 +- test/test_hash.jl | 4 +- test/test_inner.jl | 14 +- test/test_jet.jl | 4 +- test/test_memorylayout.jl | 7 +- test/test_mul_leftright.jl | 61 ++-- test/test_noisycircuits.jl | 522 +++++++++++++-------------- test/test_nonclifford.jl | 74 ++-- test/test_pauliframe.jl | 161 ++++----- test/test_paulis.jl | 9 +- test/test_precompile.jl | 6 +- test/test_projections.jl | 59 ++- test/test_quantumoptics.jl | 172 ++++----- test/test_random.jl | 102 +++--- test/test_stabcanon.jl | 12 +- test/test_stabs.jl | 166 ++++----- test/test_sumtypecompactification.jl | 5 +- test/test_symcliff.jl | 142 ++++---- test/test_symcontrolled.jl | 200 +++++----- test/test_syndromemeas.jl | 6 +- test/test_throws.jl | 93 +++-- test/test_trace.jl | 197 +++++----- 45 files changed, 1761 insertions(+), 1853 deletions(-) diff --git a/test/Project.toml b/test/Project.toml index aad3cdc34..67977b460 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -22,8 +22,8 @@ QuantumOpticsBase = "4f57444f-1401-5e15-980d-4471b28d5678" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" SIMD = "fdea26ae-647d-5447-a871-4b548cad5224" -SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" Static = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" StridedViews = "4db3bf67-4bd7-4b4e-b153-31dc3fb37143" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" diff --git a/test/runtests.jl b/test/runtests.jl index f274479a4..9d1ee89e9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,76 +1,28 @@ -using SafeTestsets using QuantumClifford +using TestItemRunner -function doset(descr) - if length(ARGS) == 0 - return true - end - for a in ARGS - if occursin(lowercase(a), lowercase(descr)) - return true - end - end - return false -end - -macro doset(descr) - quote - if doset($descr) - @safetestset $descr begin - include("test_"*$descr*".jl") - end - end - end +if get(ENV, "GPU_TESTS", "") != "true" + println("skipping gpu tests (set GPU_TESTS=true to test gpu)") end -println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREADS = $(Sys.CPU_THREADS)`...") +# filter for the test +testfilter = ti -> begin + exclude = Symbol[] + if get(ENV,"JET_TEST","")!="true" + push!(exclude, :jet) + end + if !(VERSION >= v"1.10") + push!(exclude, :doctests) + push!(exclude, :aqua) + end + if get(ENV, "GPU_TESTS", "")!="true" + push!(exclude, :gpu) + end -# in order to run the gpu tests automatically set GPU_TESTS to true in the .env file -if get(ENV, "GPU_TESTS", "") == "true" - @doset "gpu" -else - println("skipping gpu tests (set GPU_TESTS=true to test gpu)") + return all(!in(exclude), ti.tags) end -@doset "throws" -@doset "paulis" -@doset "stabs" -@doset "stabcanon" -@doset "mul_leftright" -@doset "inner" -@doset "embed" -@doset "gf2" -@doset "projections" -@doset "expect" -@doset "trace" -@doset "cliff" -@doset "symcliff" -@doset "symcontrolled" -@doset "classicalreg" -@doset "random" -@doset "noisycircuits" -@doset "syndromemeas" -@doset "bitpack" -@doset "memorylayout" -@doset "graphs" -@doset "hash" -@doset "entanglement" -@doset "enumerate" -@doset "quantumoptics" -@doset "ecc" -@doset "ecc_codeproperties" -@doset "ecc_decoder_all_setups" -@doset "ecc_encoding" -@doset "ecc_gottesman" -@doset "ecc_reedmuller" -@doset "ecc_bch" -@doset "ecc_syndromes" -@doset "ecc_throws" -@doset "precompile" -@doset "pauliframe" -@doset "sumtypecompactification" -@doset "allocations" -VERSION >= v"1.10" && @doset "doctests" -get(ENV,"JET_TEST","")=="true" && @doset "jet" -VERSION >= v"1.10" && @doset "aqua" +println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREADS = $(Sys.CPU_THREADS)`...") + +@run_package_tests filter=testfilter diff --git a/test/test_allocations.jl b/test/test_allocations.jl index 29784fa93..51a432d21 100644 --- a/test/test_allocations.jl +++ b/test/test_allocations.jl @@ -1,7 +1,5 @@ -using QuantumClifford -using QuantumClifford: mul_left! - -@testset "Allocation checks" begin +@testitem "Allocation checks" begin + using QuantumClifford: mul_left! n = Threads.nthreads() allocated(f::F) where {F} = @allocated f() @testset "apply! mul_left! canonicalize!" begin diff --git a/test/test_aqua.jl b/test/test_aqua.jl index 1032c34f3..93c2b43be 100644 --- a/test/test_aqua.jl +++ b/test/test_aqua.jl @@ -1,3 +1,4 @@ -using Aqua -using QuantumClifford -Aqua.test_all(QuantumClifford) +@testitem "Aqua" tags=[:aqua] begin + using Aqua + Aqua.test_all(QuantumClifford) +end diff --git a/test/test_bitpack.jl b/test/test_bitpack.jl index 4b9ab5d12..d56056d61 100644 --- a/test/test_bitpack.jl +++ b/test/test_bitpack.jl @@ -1,49 +1,50 @@ -using Random -using QuantumClifford -using QuantumClifford: Tableau +@testitem "Alternative bit packing" begin + using Random + using QuantumClifford: Tableau -@testset "Alternative bit packing" begin - for n in [1,3] # can not go higher than 4 (limitation from SIMD acting on transposed/strided arrays) - N = 64*n-2 - s64 = random_stabilizer(N,N); - _phases = phases(s64); - xzs64 = tab(s64).xzs; - xzs64T = collect(xzs64')'; - p64 = random_pauli(N;nophase=true); - c64_stab = tab(random_destabilizer(N;phases=false)); + @testset "alternative bit packing" begin + for n in [1,3] # can not go higher than 4 (limitation from SIMD acting on transposed/strided arrays) + N = 64*n-2 + s64 = random_stabilizer(N,N); + _phases = phases(s64); + xzs64 = tab(s64).xzs; + xzs64T = collect(xzs64')'; + p64 = random_pauli(N;nophase=true); + c64_stab = tab(random_destabilizer(N;phases=false)); - _after_p = p64*s64 - after_p = stab_to_gf2(_after_p); - after_p_phases = phases(_after_p); - after_can = stab_to_gf2(canonicalize!(copy(s64))); - _after_clif = apply!(copy(s64),CliffordOperator(c64_stab)); - after_cliff = stab_to_gf2(_after_clif); - after_cliff_phases = phases(_after_clif); + _after_p = p64*s64 + after_p = stab_to_gf2(_after_p); + after_p_phases = phases(_after_p); + after_can = stab_to_gf2(canonicalize!(copy(s64))); + _after_clif = apply!(copy(s64),CliffordOperator(c64_stab)); + after_cliff = stab_to_gf2(_after_clif); + after_cliff_phases = phases(_after_clif); - for int in [UInt8, UInt16, UInt32, UInt64] - p = PauliOperator(p64.phase, N, collect(reinterpret(int,p64.xz))); - xzs = collect(reinterpret(int, collect(xzs64))); - xzsT = collect(xzs')'; - _s = Stabilizer(_phases,N,xzs); - _sT = Stabilizer(_phases,N,xzsT); + for int in [UInt8, UInt16, UInt32, UInt64] + p = PauliOperator(p64.phase, N, collect(reinterpret(int,p64.xz))); + xzs = collect(reinterpret(int, collect(xzs64))); + xzsT = collect(xzs')'; + _s = Stabilizer(_phases,N,xzs); + _sT = Stabilizer(_phases,N,xzsT); - for trans in (true, false) - s = trans ? _sT : _s - apply_pauli = p*s - @test after_p_phases == phases(apply_pauli) - canon = canonicalize!(deepcopy(s)) - @test after_can == stab_to_gf2(canon) + for trans in (true, false) + s = trans ? _sT : _s + apply_pauli = p*s + @test after_p_phases == phases(apply_pauli) + canon = canonicalize!(deepcopy(s)) + @test after_can == stab_to_gf2(canon) - cxzs = collect(reinterpret(int, collect(c64_stab.xzs))); - cxzsT = collect(cxzs')'; - _c = CliffordOperator(Tableau(zeros(UInt8,2N),N,cxzs)); - _cT = CliffordOperator(Tableau(zeros(UInt8,2N),N,cxzsT)); + cxzs = collect(reinterpret(int, collect(c64_stab.xzs))); + cxzsT = collect(cxzs')'; + _c = CliffordOperator(Tableau(zeros(UInt8,2N),N,cxzs)); + _cT = CliffordOperator(Tableau(zeros(UInt8,2N),N,cxzsT)); - for ctrans in (true, false) - c = ctrans ? _c : _cT - after_clifford = apply!(deepcopy(s),c) - @test after_cliff == stab_to_gf2(after_clifford) - @test after_cliff_phases == phases(after_clifford) + for ctrans in (true, false) + c = ctrans ? _c : _cT + after_clifford = apply!(deepcopy(s),c) + @test after_cliff == stab_to_gf2(after_clifford) + @test after_cliff_phases == phases(after_clifford) + end end end end diff --git a/test/test_classicalreg.jl b/test/test_classicalreg.jl index 18530fc4f..473da4109 100644 --- a/test/test_classicalreg.jl +++ b/test/test_classicalreg.jl @@ -1,27 +1,27 @@ -using Random -using QuantumClifford +@testitem "Classical" begin + using Random + using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good -using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good - -n=5 -stab = random_stabilizer(n) -mdstab = MixedDestabilizer(stab) -_ = Register(stab) -reg = Register(stab, [0,0,0,0]) -regmd = Register(mdstab, [0,0,0,0]) -@test reg==regmd -@test stabilizerview(reg) == stabilizerview(mdstab) -@test destabilizerview(reg) == destabilizerview(mdstab) -@test logicalxview(reg) == logicalxview(mdstab) -@test logicalzview(reg) == logicalzview(mdstab) -@test bitview(reg) == bitview(regmd) -@test quantumstate(reg) == mdstab -for state in [mdstab,reg,regmd] - for op in [sMX(1,1),sMY(2,2),sMZ(3,3),PauliMeasurement(P"XYZZZ",4),sCNOT(1,2),sCPHASE(2,3),sCNOT(3,4),NoiseOpAll(UnbiasedUncorrelatedNoise(0.1))] - apply!(state,op) - end - for (i,proj) in enumerate([projectXrand!, projectYrand!, projectZrand!]) - proj(state, i) + n=5 + stab = random_stabilizer(n) + mdstab = MixedDestabilizer(stab) + _ = Register(stab) + reg = Register(stab, [0,0,0,0]) + regmd = Register(mdstab, [0,0,0,0]) + @test reg==regmd + @test stabilizerview(reg) == stabilizerview(mdstab) + @test destabilizerview(reg) == destabilizerview(mdstab) + @test logicalxview(reg) == logicalxview(mdstab) + @test logicalzview(reg) == logicalzview(mdstab) + @test bitview(reg) == bitview(regmd) + @test quantumstate(reg) == mdstab + for state in [mdstab,reg,regmd] + for op in [sMX(1,1),sMY(2,2),sMZ(3,3),PauliMeasurement(P"XYZZZ",4),sCNOT(1,2),sCPHASE(2,3),sCNOT(3,4),NoiseOpAll(UnbiasedUncorrelatedNoise(0.1))] + apply!(state,op) + end + for (i,proj) in enumerate([projectXrand!, projectYrand!, projectZrand!]) + proj(state, i) + end end + @test tab(canonicalize!(stabilizerview(reg))).xzs == tab(canonicalize!(stabilizerview(mdstab))).xzs end -@test tab(canonicalize!(stabilizerview(reg))).xzs == tab(canonicalize!(stabilizerview(mdstab))).xzs diff --git a/test/test_cliff.jl b/test/test_cliff.jl index 1a47e7ac5..74b1a1a46 100644 --- a/test/test_cliff.jl +++ b/test/test_cliff.jl @@ -1,107 +1,107 @@ -using Random -using QuantumClifford +@testitem "Clifford" begin + using Random + using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good + using QuantumClifford: mul_left! -using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good -using QuantumClifford: mul_left! + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. - -@testset "Low-level tableaux ops" begin - for n in test_sizes - p1 = random_pauli(n) - p11 = copy(p1) - p2 = random_pauli(n) - p3 = p2*p1 - s = Stabilizer([p1,p2]) - @test QuantumClifford._mul_left_nonvec!(copy(p1).xz,p2.xz)&3 == mul_left!(copy(p1).xz,p2.xz)&3 - @test prodphase(p2,p1) == mul_left!(p1,p2).phase[] - mul_left!(p11,s,2) - mul_left!(s,1,2) - @test p1 == p11 == p3 == s[1] - end -end -@testset "Clifford Operators" begin - @testset "Constructors" begin - @test_throws DimensionMismatch CliffordOperator(T"X") + @testset "Low-level tableaux ops" begin + for n in test_sizes + p1 = random_pauli(n) + p11 = copy(p1) + p2 = random_pauli(n) + p3 = p2*p1 + s = Stabilizer([p1,p2]) + @test QuantumClifford._mul_left_nonvec!(copy(p1).xz,p2.xz)&3 == mul_left!(copy(p1).xz,p2.xz)&3 + @test prodphase(p2,p1) == mul_left!(p1,p2).phase[] + mul_left!(p11,s,2) + mul_left!(s,1,2) + @test p1 == p11 == p3 == s[1] + end end - @testset "Permutations of qubits" begin - for c in [tCNOT, tId1⊗tHadamard, tCNOT⊗tCNOT, tensor_pow(tCNOT,6), tensor_pow(tCNOT,7), tensor_pow(tCNOT,6)⊗tPhase, tensor_pow(tCNOT,7)⊗tPhase] - for rep in 1:5 - p = randperm(nqubits(c)) - s = random_stabilizer(nqubits(c)) - @test permute(c,p)*s[:,p] == (c*s)[:,p] - end + @testset "Clifford Operators" begin + @testset "Constructors" begin + @test_throws DimensionMismatch CliffordOperator(T"X") end - for i in 1:5 - p = randperm(125) - c = rand([tId1, tHadamard, tPhase], 125) - @test ⊗(c[p]...) == permute(⊗(c...), p) + @testset "Permutations of qubits" begin + for c in [tCNOT, tId1⊗tHadamard, tCNOT⊗tCNOT, tensor_pow(tCNOT,6), tensor_pow(tCNOT,7), tensor_pow(tCNOT,6)⊗tPhase, tensor_pow(tCNOT,7)⊗tPhase] + for rep in 1:5 + p = randperm(nqubits(c)) + s = random_stabilizer(nqubits(c)) + @test permute(c,p)*s[:,p] == (c*s)[:,p] + end + end + for i in 1:5 + p = randperm(125) + c = rand([tId1, tHadamard, tPhase], 125) + @test ⊗(c[p]...) == permute(⊗(c...), p) + end end - end - @testset "Tensor products" begin - for n in test_sizes - for np in [2,3,4] - for pow in [1,2,10] - s1 = random_stabilizer(n) - sps = [random_stabilizer(np) for i in 1:pow] - ss = [s1, sps...] - c1 = random_clifford(n) - cps = repeat([random_clifford(np)],pow) - cs = [c1, cps...] - res1 = ⊗([c*s for (c,s) in zip(cs,ss)]...) - res2 = ⊗(cs...)*⊗(ss...) - res3 = (c1*s1) ⊗ (⊗(tensor_pow(cps[1],pow)) * ⊗(sps...)) - @test res1==res2==res3 + @testset "Tensor products" begin + for n in test_sizes + for np in [2,3,4] + for pow in [1,2,10] + s1 = random_stabilizer(n) + sps = [random_stabilizer(np) for i in 1:pow] + ss = [s1, sps...] + c1 = random_clifford(n) + cps = repeat([random_clifford(np)],pow) + cs = [c1, cps...] + res1 = ⊗([c*s for (c,s) in zip(cs,ss)]...) + res2 = ⊗(cs...)*⊗(ss...) + res3 = (c1*s1) ⊗ (⊗(tensor_pow(cps[1],pow)) * ⊗(sps...)) + @test res1==res2==res3 + end end end end - end - @testset "Clifford acting on Stabilizer" begin - for size in test_sizes - size < 5 && continue - s = random_stabilizer(size) - gates = vcat([tCNOT, tHadamard, tPhase], repeat([tId1],size-4)) - gates_perm = randperm(size-1) - gates = gates[gates_perm] - big_gate = reduce(⊗,gates) + @testset "Clifford acting on Stabilizer" begin + for size in test_sizes + size < 5 && continue + s = random_stabilizer(size) + gates = vcat([tCNOT, tHadamard, tPhase], repeat([tId1],size-4)) + gates_perm = randperm(size-1) + gates = gates[gates_perm] + big_gate = reduce(⊗,gates) - s1 = apply!(copy(s),big_gate) - @test stab_looks_good(s1) + s1 = apply!(copy(s),big_gate) + @test stab_looks_good(s1) - igates_perm = invperm(gates_perm) - s2 = copy(s) - canonicalize!(s2) - s2 = apply!(s2, tCNOT, [igates_perm[1],igates_perm[1]+1]) - canonicalize!(s2) - s2 = apply!(s2, tHadamard, [igates_perm[2]+(igates_perm[1]PyBeliefPropOSDecoder(c, maxiter=10)] + e = evaluate_decoder(d(c), s, 100000) + @show c + @show s + @show e + @assert max(e...) < noise/4 + end + end + end + end + + ## -include("test_ecc_base.jl") + using Test + using QuantumClifford + using QuantumClifford.ECC -@testset "table decoder, good for small codes" begin - codes = [ - all_testablable_code_instances(;maxn=10)... - ] + import PyQDecoders + import LDPCDecoders - noise = 0.001 + @testset "matching decoder, good as long as column weight of the code is limited" begin + codes = [ + Toric(8,8), + Toric(9,9), + Surface(8,8), + Surface(9,9) + ] - setups = [ - CommutationCheckECCSetup(noise), - NaiveSyndromeECCSetup(noise, 0), - ShorSyndromeECCSetup(noise, 0), - ] + noise = 0.01 - for c in codes - for s in setups - for d in [TableDecoder] - e = evaluate_decoder(d(c), s, 100000) + setups = [ + CommutationCheckECCSetup(noise), + NaiveSyndromeECCSetup(noise, 0), + ShorSyndromeECCSetup(noise, 0), + ] + + for c in codes + for s in setups + e = evaluate_decoder(PyMatchingDecoder(c), s, 10000) #@show c #@show s #@show e - @assert max(e...) < noise/4 + @assert max(e...) < noise/5 end end end end - -## - -@testset "belief prop decoders, good for sparse codes" begin - codes = [ - # TODO - ] - - noise = 0.001 - - setups = [ - CommutationCheckECCSetup(noise), - NaiveSyndromeECCSetup(noise, 0), - ShorSyndromeECCSetup(noise, 0), - ] - - for c in codes - for s in setups - for d in [c->PyBeliefPropOSDecoder(c, maxiter=10)] - e = evaluate_decoder(d(c), s, 100000) - @show c - @show s - @show e - @assert max(e...) < noise/4 - end - end - end -end - -## - -using Test -using QuantumClifford -using QuantumClifford.ECC - -import PyQDecoders -import LDPCDecoders - -@testset "matching decoder, good as long as column weight of the code is limited" begin - codes = [ - Toric(8,8), - Toric(9,9), - Surface(8,8), - Surface(9,9) - ] - - noise = 0.01 - - setups = [ - CommutationCheckECCSetup(noise), - NaiveSyndromeECCSetup(noise, 0), - ShorSyndromeECCSetup(noise, 0), - ] - - for c in codes - for s in setups - e = evaluate_decoder(PyMatchingDecoder(c), s, 10000) - #@show c - #@show s - #@show e - @assert max(e...) < noise/5 - end - end -end diff --git a/test/test_ecc_encoding.jl b/test/test_ecc_encoding.jl index ed16947de..cab0562ff 100644 --- a/test/test_ecc_encoding.jl +++ b/test/test_ecc_encoding.jl @@ -1,10 +1,10 @@ -using Test -using QuantumClifford -using QuantumClifford.ECC +@testitem "encoding circuits - compare to algebraic construction of encoded state" begin + using QuantumClifford + using QuantumClifford.ECC + + include("test_ecc_base.jl") -include("test_ecc_base.jl") -@testset "encoding circuits - compare to algebraic construction of encoded state" begin # This test verifies that logical measurements on an encoded state match the physical pre-encoded state. # This test skips verifying the permutations of qubits during canonicalization are properly undone, # i.e. we modify the code we are testing so that the canonicalization does not need any permutations. diff --git a/test/test_ecc_gottesman.jl b/test/test_ecc_gottesman.jl index e290a87f4..6f31a993c 100644 --- a/test/test_ecc_gottesman.jl +++ b/test/test_ecc_gottesman.jl @@ -1,11 +1,8 @@ -using Test -using QuantumClifford -using QuantumClifford: mul_left! -using QuantumClifford.ECC -using QuantumClifford.ECC: AbstractECC +@testitem "Gottesman codes should correct all single-qubit errors" begin + using QuantumClifford: mul_left! + using QuantumClifford.ECC + using QuantumClifford.ECC: AbstractECC - -@testset "Gottesman codes should correct all single-qubit errors" begin for j in 3:12 H = parity_checks(Gottesman(j)) syndromes = Set([]) # the set automatically removes repeated entries diff --git a/test/test_ecc_reedmuller.jl b/test/test_ecc_reedmuller.jl index 7f148a2d2..12031d71f 100644 --- a/test/test_ecc_reedmuller.jl +++ b/test/test_ecc_reedmuller.jl @@ -1,68 +1,68 @@ -using Test -using Nemo -using Combinatorics -using LinearAlgebra -using QuantumClifford -using QuantumClifford.ECC -using QuantumClifford.ECC: AbstractECC, ReedMuller +@testitem "Reed-Muller" begin + using Nemo + using Combinatorics + using LinearAlgebra + using QuantumClifford.ECC + using QuantumClifford.ECC: AbstractECC, ReedMuller -function binomial_coeff_sum(r, m) - total = 0 - for i in 0:r - total += length(combinations(1:m, i)) + function binomial_coeff_sum(r, m) + total = 0 + for i in 0:r + total += length(combinations(1:m, i)) + end + return total end - return total -end -@testset "Test RM(r, m) Matrix Rank" begin - for m in 2:5 - for r in 0:m - 1 - H = parity_checks(ReedMuller(r, m)) - mat = Nemo.matrix(Nemo.GF(2), H) - computed_rank = LinearAlgebra.rank(mat) - expected_rank = binomial_coeff_sum(r, m) - @test computed_rank == expected_rank + @testset "Test RM(r, m) Matrix Rank" begin + for m in 2:5 + for r in 0:m - 1 + H = parity_checks(ReedMuller(r, m)) + mat = Nemo.matrix(Nemo.GF(2), H) + computed_rank = LinearAlgebra.rank(mat) + expected_rank = binomial_coeff_sum(r, m) + @test computed_rank == expected_rank + end end end -end -@testset "Testing common examples of RM(r,m) codes [raaphorst2003reed](@cite), [djordjevic2021quantum](@cite), [abbe2020reed](@cite)" begin - - #RM(0,3) - @test parity_checks(ReedMuller(0,3)) == [1 1 1 1 1 1 1 1] - - #RM(1,3) - @test parity_checks(ReedMuller(1,3)) == [1 1 1 1 1 1 1 1; - 1 1 1 1 0 0 0 0; - 1 1 0 0 1 1 0 0; - 1 0 1 0 1 0 1 0] - #RM(2,3) - @test parity_checks(ReedMuller(2,3)) == [1 1 1 1 1 1 1 1; - 1 1 1 1 0 0 0 0; - 1 1 0 0 1 1 0 0; - 1 0 1 0 1 0 1 0; - 1 1 0 0 0 0 0 0; - 1 0 1 0 0 0 0 0; - 1 0 0 0 1 0 0 0] - #RM(3,3) - @test parity_checks(ReedMuller(3,3)) == [1 1 1 1 1 1 1 1; - 1 1 1 1 0 0 0 0; - 1 1 0 0 1 1 0 0; - 1 0 1 0 1 0 1 0; - 1 1 0 0 0 0 0 0; - 1 0 1 0 0 0 0 0; - 1 0 0 0 1 0 0 0; - 1 0 0 0 0 0 0 0] - #RM(2,4) - @test parity_checks(ReedMuller(2,4)) == [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1; - 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0; - 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0; - 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0; - 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0; - 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0; - 1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0; - 1 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0; - 1 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0; - 1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0; - 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0] + @testset "Testing common examples of RM(r,m) codes [raaphorst2003reed](@cite), [djordjevic2021quantum](@cite), [abbe2020reed](@cite)" begin + + #RM(0,3) + @test parity_checks(ReedMuller(0,3)) == [1 1 1 1 1 1 1 1] + + #RM(1,3) + @test parity_checks(ReedMuller(1,3)) == [1 1 1 1 1 1 1 1; + 1 1 1 1 0 0 0 0; + 1 1 0 0 1 1 0 0; + 1 0 1 0 1 0 1 0] + #RM(2,3) + @test parity_checks(ReedMuller(2,3)) == [1 1 1 1 1 1 1 1; + 1 1 1 1 0 0 0 0; + 1 1 0 0 1 1 0 0; + 1 0 1 0 1 0 1 0; + 1 1 0 0 0 0 0 0; + 1 0 1 0 0 0 0 0; + 1 0 0 0 1 0 0 0] + #RM(3,3) + @test parity_checks(ReedMuller(3,3)) == [1 1 1 1 1 1 1 1; + 1 1 1 1 0 0 0 0; + 1 1 0 0 1 1 0 0; + 1 0 1 0 1 0 1 0; + 1 1 0 0 0 0 0 0; + 1 0 1 0 0 0 0 0; + 1 0 0 0 1 0 0 0; + 1 0 0 0 0 0 0 0] + #RM(2,4) + @test parity_checks(ReedMuller(2,4)) == [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1; + 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0; + 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0; + 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0; + 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0; + 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0; + 1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0; + 1 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0; + 1 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0; + 1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0; + 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0] + end end diff --git a/test/test_ecc_syndromes.jl b/test/test_ecc_syndromes.jl index f58402656..45f4e0f89 100644 --- a/test/test_ecc_syndromes.jl +++ b/test/test_ecc_syndromes.jl @@ -1,49 +1,49 @@ -using Test -using QuantumClifford -using QuantumClifford: mul_left!, embed -using QuantumClifford.ECC -using QuantumClifford.ECC: AbstractECC +@testitem "ECC Syndromes" begin + using QuantumClifford: mul_left!, embed + using QuantumClifford.ECC + using QuantumClifford.ECC: AbstractECC -include("test_ecc_base.jl") + include("test_ecc_base.jl") -function pframe_naive_vs_shor_syndrome(code) - ecirc = naive_encoding_circuit(code) - naive_scirc, naive_ancillaries = naive_syndrome_circuit(code) - shor_cat_scirc, shor_scirc, shor_ancillaries, shor_bits = shor_syndrome_circuit(code) - nframes = 10 - dataqubits = code_n(code) - syndromebits = code_s(code) - naive_qubits = dataqubits + syndromebits - shor_qubits = dataqubits + shor_ancillaries - # no noise - naive_frames = PauliFrame(nframes, naive_qubits, syndromebits) - shor_frames = PauliFrame(nframes, shor_qubits, last(shor_bits)) - naive_circuit = [ecirc..., naive_scirc...] - shor_circuit = [ecirc..., shor_cat_scirc..., shor_scirc...] - pftrajectories(naive_frames, naive_circuit) - pftrajectories(shor_frames, shor_circuit) - @test pfmeasurements(naive_frames) == pfmeasurements(shor_frames)[:,shor_bits] - # with errors - for _ in 1:10 + function pframe_naive_vs_shor_syndrome(code) + ecirc = naive_encoding_circuit(code) + naive_scirc, naive_ancillaries = naive_syndrome_circuit(code) + shor_cat_scirc, shor_scirc, shor_ancillaries, shor_bits = shor_syndrome_circuit(code) + nframes = 10 + dataqubits = code_n(code) + syndromebits = code_s(code) + naive_qubits = dataqubits + syndromebits + shor_qubits = dataqubits + shor_ancillaries + # no noise naive_frames = PauliFrame(nframes, naive_qubits, syndromebits) shor_frames = PauliFrame(nframes, shor_qubits, last(shor_bits)) - pftrajectories(naive_frames, ecirc) - pftrajectories(shor_frames, [ecirc..., shor_cat_scirc...]) - # manually injecting the same type of noise in the frames -- not really a user accessible API - p = random_pauli(dataqubits, realphase=true) - pₙ = embed(naive_qubits, 1:dataqubits, p) - pₛ = embed(shor_qubits, 1:dataqubits, p) - mul_left!(naive_frames.frame, pₙ) - mul_left!(shor_frames.frame, pₛ) - # run the syndrome circuits using the public API - pftrajectories(naive_frames, naive_scirc) - pftrajectories(shor_frames, shor_scirc) + naive_circuit = [ecirc..., naive_scirc...] + shor_circuit = [ecirc..., shor_cat_scirc..., shor_scirc...] + pftrajectories(naive_frames, naive_circuit) + pftrajectories(shor_frames, shor_circuit) @test pfmeasurements(naive_frames) == pfmeasurements(shor_frames)[:,shor_bits] + # with errors + for _ in 1:10 + naive_frames = PauliFrame(nframes, naive_qubits, syndromebits) + shor_frames = PauliFrame(nframes, shor_qubits, last(shor_bits)) + pftrajectories(naive_frames, ecirc) + pftrajectories(shor_frames, [ecirc..., shor_cat_scirc...]) + # manually injecting the same type of noise in the frames -- not really a user accessible API + p = random_pauli(dataqubits, realphase=true) + pₙ = embed(naive_qubits, 1:dataqubits, p) + pₛ = embed(shor_qubits, 1:dataqubits, p) + mul_left!(naive_frames.frame, pₙ) + mul_left!(shor_frames.frame, pₛ) + # run the syndrome circuits using the public API + pftrajectories(naive_frames, naive_scirc) + pftrajectories(shor_frames, shor_scirc) + @test pfmeasurements(naive_frames) == pfmeasurements(shor_frames)[:,shor_bits] + end end -end -@testset "naive and shor measurement circuits" begin - for c in all_testablable_code_instances() - pframe_naive_vs_shor_syndrome(c) + @testset "naive and shor measurement circuits" begin + for c in all_testablable_code_instances() + pframe_naive_vs_shor_syndrome(c) + end end end diff --git a/test/test_ecc_throws.jl b/test/test_ecc_throws.jl index 19468e1ad..bd648327b 100644 --- a/test/test_ecc_throws.jl +++ b/test/test_ecc_throws.jl @@ -1,9 +1,9 @@ -using Test -using QuantumClifford -using QuantumClifford.ECC: ReedMuller, BCH +@testitem "ECC throws" begin + using QuantumClifford.ECC: ReedMuller, BCH -@test_throws ArgumentError ReedMuller(-1, 3) -@test_throws ArgumentError ReedMuller(1, 0) + @test_throws ArgumentError ReedMuller(-1, 3) + @test_throws ArgumentError ReedMuller(1, 0) -@test_throws ArgumentError BCH(2, 2) -@test_throws ArgumentError BCH(3, 4) + @test_throws ArgumentError BCH(2, 2) + @test_throws ArgumentError BCH(3, 4) +end diff --git a/test/test_embed.jl b/test/test_embed.jl index a4c6e39e6..37d5c0df7 100644 --- a/test/test_embed.jl +++ b/test/test_embed.jl @@ -1,7 +1,4 @@ -using Test -using QuantumClifford - -@testset "embed PauliOperator" begin +@testitem "embed PauliOperator" begin @test embed(5,3,P"-Y") == P"-__Y__" @test embed(5,(3,5),P"-YZ") == P"-__Y_Z" @test embed(5,[3,5],P"-YZ") == P"-__Y_Z" diff --git a/test/test_entanglement.jl b/test/test_entanglement.jl index 7441b638c..fa50fb369 100644 --- a/test/test_entanglement.jl +++ b/test/test_entanglement.jl @@ -1,54 +1,53 @@ -using Test +@testitem "Entanglement" begin + using Graphs -using Graphs -using QuantumClifford + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. + using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good -using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good - -@testset "Clipped gauge of stabilizer states" begin - for n in test_sizes - s = random_stabilizer(n) - s_clipped = copy(s) - canonicalize_clip!(s_clipped) - @test logdot(s, s_clipped)==0 - @test stab_looks_good(s_clipped) - @test canonicalize!(copy(s_clipped))==canonicalize!(copy(s)) - bg = bigram(s_clipped; clip=false) - rows, columns = size(stabilizerview(s_clipped)) - @test all(count(==(j), bg)==2 for j in 1:columns) + @testset "Clipped gauge of stabilizer states" begin + for n in test_sizes + s = random_stabilizer(n) + s_clipped = copy(s) + canonicalize_clip!(s_clipped) + @test logdot(s, s_clipped)==0 + @test stab_looks_good(s_clipped) + @test canonicalize!(copy(s_clipped))==canonicalize!(copy(s)) + bg = bigram(s_clipped; clip=false) + rows, columns = size(stabilizerview(s_clipped)) + @test all(count(==(j), bg)==2 for j in 1:columns) + end end -end -@testset "Entanglement calculated from clipped/rref for mixed states" begin - for n in test_sizes - s = random_destabilizer(rand(1:n), n) - endpoints = rand(1:n, 2) - subsystem_range = min(endpoints...):max(endpoints...) - @test entanglement_entropy(copy(s), subsystem_range, Val(:clip)) == entanglement_entropy(copy(s), subsystem_range, Val(:rref)) + @testset "Entanglement calculated from clipped/rref for mixed states" begin + for n in test_sizes + s = random_destabilizer(rand(1:n), n) + endpoints = rand(1:n, 2) + subsystem_range = min(endpoints...):max(endpoints...) + @test entanglement_entropy(copy(s), subsystem_range, Val(:clip)) == entanglement_entropy(copy(s), subsystem_range, Val(:rref)) + end end -end -@testset "Entanglement calculated from clipped/graph/rref for pure states" begin - for n in test_sizes - s = random_stabilizer(n) - endpoints = rand(1:n, 2) - subsystem_range = min(endpoints...):max(endpoints...) - @test entanglement_entropy(copy(s), subsystem_range, Val(:clip)) == entanglement_entropy(copy(s), subsystem_range, Val(:graph)) == entanglement_entropy(copy(s), subsystem_range, Val(:rref)) + @testset "Entanglement calculated from clipped/graph/rref for pure states" begin + for n in test_sizes + s = random_stabilizer(n) + endpoints = rand(1:n, 2) + subsystem_range = min(endpoints...):max(endpoints...) + @test entanglement_entropy(copy(s), subsystem_range, Val(:clip)) == entanglement_entropy(copy(s), subsystem_range, Val(:graph)) == entanglement_entropy(copy(s), subsystem_range, Val(:rref)) + end end -end -@testset "Entanglement of special cases" begin - s = S" + @testset "Entanglement of special cases" begin + s = S" + XZZ_ZZ + ZX_ZZ_ + Z_XZ_Z + _ZZXZZ + ZZ_ZXZ + Z_ZZZX" - subsystem = 1:3 - @test entanglement_entropy(copy(s), subsystem, Val(:clip))==2 - @test entanglement_entropy(copy(s), subsystem, Val(:graph))==2 - @test entanglement_entropy(copy(s), subsystem, Val(:rref))==2 + subsystem = 1:3 + @test entanglement_entropy(copy(s), subsystem, Val(:clip))==2 + @test entanglement_entropy(copy(s), subsystem, Val(:graph))==2 + @test entanglement_entropy(copy(s), subsystem, Val(:rref))==2 + end end diff --git a/test/test_enumerate.jl b/test/test_enumerate.jl index b3a424337..b1c0ac932 100644 --- a/test/test_enumerate.jl +++ b/test/test_enumerate.jl @@ -1,6 +1,4 @@ -using QuantumClifford - -@testset "Enumeration" begin +@testitem "Enumeration" begin @test Set([copy(op) for op in enumerate_cliffords(1)]) == Set((random_clifford(1,phases=false) for i in 1:70)) @test Set([copy(op) for op in enumerate_phases(enumerate_cliffords(1))]) == Set((random_clifford(1,phases=true) for i in 1:300)) @test length(collect(enumerate_cliffords(2))) == length(collect(enumerate_phases(enumerate_cliffords(2))))/2^4 == 720 diff --git a/test/test_expect.jl b/test/test_expect.jl index 6efdf280f..8f1ff9836 100644 --- a/test/test_expect.jl +++ b/test/test_expect.jl @@ -1,35 +1,33 @@ -using QuantumClifford +@testite "Expectation of Pauli strings on stabilizer states" begin + using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good + using QuantumClifford: projectremoverand! -using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good -using QuantumClifford: projectremoverand! + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. - -function alter_expect(p::PauliOperator, s::Stabilizer) - nqubits(p) == nqubits(s) || error("The number of qubits does not match") - n = nqubits(p) - s_anc = s ⊗ one(Stabilizer, n) - p_anc = zero(PauliOperator, 2n) - for i = 1:n - if p[i] != (false, false) - if p[i][1] - apply!(s_anc, sHadamard(i)) - p[i][2] && apply!(s_anc, sInvPhase(i)) + function alter_expect(p::PauliOperator, s::Stabilizer) + nqubits(p) == nqubits(s) || error("The number of qubits does not match") + n = nqubits(p) + s_anc = s ⊗ one(Stabilizer, n) + p_anc = zero(PauliOperator, 2n) + for i = 1:n + if p[i] != (false, false) + if p[i][1] + apply!(s_anc, sHadamard(i)) + p[i][2] && apply!(s_anc, sInvPhase(i)) + end + apply!(s_anc, sCNOT(i, i+n)) + p_anc[i+n] = (false, true) end - apply!(s_anc, sCNOT(i, i+n)) - p_anc[i+n] = (false, true) end + p_anc.phase[] = p.phase[] + _, _, result = project!(s_anc, p_anc) + result === nothing && return 0 + result === 0x00 && return 1 + result === 0x01 && return im + result === 0x02 && return -1 + result === 0x03 && return -im end - p_anc.phase[] = p.phase[] - _, _, result = project!(s_anc, p_anc) - result === nothing && return 0 - result === 0x00 && return 1 - result === 0x01 && return im - result === 0x02 && return -1 - result === 0x03 && return -im -end -@testset "Expectation of Pauli strings on stabilizer states" begin st = bell() apply!(st, sX(2)) @test expect(P"XX", st) == alter_expect(P"XX", st) == 1 diff --git a/test/test_gf2.jl b/test/test_gf2.jl index 35c3f580a..45aa6701b 100644 --- a/test/test_gf2.jl +++ b/test/test_gf2.jl @@ -1,34 +1,27 @@ -using Random -using QuantumClifford +@testitem "GF(2) representations" begin + using Random + using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good - -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. - -function test_gf2() - @testset "GF(2) representations" begin - @testset "Equivalence of GF(2) Gaussian elimination and Stabilizer canonicalization" begin - for n in test_sizes - for rep in 1:5 - s = random_stabilizer(n)[randperm(n)[1:rand(n÷2+1:n)]] - cs = canonicalize!(copy(s)); - H = stab_to_gf2(cs); - cH = gf2_gausselim!(stab_to_gf2(s)); - @test H==cH - end + @testset "Equivalence of GF(2) Gaussian elimination and Stabilizer canonicalization" begin + for n in test_sizes + for rep in 1:5 + s = random_stabilizer(n)[randperm(n)[1:rand(n÷2+1:n)]] + cs = canonicalize!(copy(s)); + H = stab_to_gf2(cs); + cH = gf2_gausselim!(stab_to_gf2(s)); + @test H==cH end end - @testset "GF(2) H and G matrices" begin - for n in test_sizes - for rep in 1:5 - H = random_invertible_gf2(n)[randperm(n)[1:rand(n÷2+1:n)],:] - H = gf2_gausselim!(H) - G = gf2_H_to_G(H) - @test sum(G*H' .%2)==0; - end + end + @testset "GF(2) H and G matrices" begin + for n in test_sizes + for rep in 1:5 + H = random_invertible_gf2(n)[randperm(n)[1:rand(n÷2+1:n)],:] + H = gf2_gausselim!(H) + G = gf2_H_to_G(H) + @test sum(G*H' .%2)==0; end end end end - -test_gf2() diff --git a/test/test_gpu.jl b/test/test_gpu.jl index 042d94b45..3a819cf40 100644 --- a/test/test_gpu.jl +++ b/test/test_gpu.jl @@ -1,147 +1,147 @@ -using Test -using QuantumClifford -using QuantumClifford: to_cpu, to_gpu -using CUDA +@testitem "GPU" tags=[:gpu] begin + using QuantumClifford: to_cpu, to_gpu + using CUDA -function apply_single_qubit_and_compare(n, s, s_gpu) - qbitidx = (((rand(Int) % n) + n) %n) + 1 - op = random_clifford1(qbitidx) - apply!(s_gpu, op) - apply!(s, op) - if to_cpu(s_gpu) != s - throw("result of cpu and gpu differ in single qubit operation. n=$n iteration=$iteration operation=$op") - end -end - -function apply_single_or_double_qubit_and_compare(n, s, s_gpu) - op = if rand(Int) % 2 == 0 + function apply_single_qubit_and_compare(n, s, s_gpu) qbitidx = (((rand(Int) % n) + n) %n) + 1 - random_clifford1(qbitidx) - else - # todo what other gates can we use? how to randomly generate two qubit operation? - # can we use random_clifford(2) with this function? - qbitidx1 = (((rand(Int) % n) + n) %n) + 1 - qbitidx2 = (((rand(Int) % n) + n) %n) + 1 - if qbitidx1 == qbitidx2 - qbitidx2 = (qbitidx1 % n) + 1 + op = random_clifford1(qbitidx) + apply!(s_gpu, op) + apply!(s, op) + if to_cpu(s_gpu) != s + throw("result of cpu and gpu differ in single qubit operation. n=$n iteration=$iteration operation=$op") end - sCNOT(qbitidx1, qbitidx2) - end - apply!(s_gpu, op) - apply!(s, op) - if to_cpu(s_gpu) != s - throw("result of cpu and gpu differ in single qubit operation. n=$n iteration=$iteration operation=$op") end -end -function approx(a, b, threshold) - maximum(abs.(a - b)) <= threshold -end + function apply_single_or_double_qubit_and_compare(n, s, s_gpu) + op = if rand(Int) % 2 == 0 + qbitidx = (((rand(Int) % n) + n) %n) + 1 + random_clifford1(qbitidx) + else + # todo what other gates can we use? how to randomly generate two qubit operation? + # can we use random_clifford(2) with this function? + qbitidx1 = (((rand(Int) % n) + n) %n) + 1 + qbitidx2 = (((rand(Int) % n) + n) %n) + 1 + if qbitidx1 == qbitidx2 + qbitidx2 = (qbitidx1 % n) + 1 + end + sCNOT(qbitidx1, qbitidx2) + end + apply!(s_gpu, op) + apply!(s, op) + if to_cpu(s_gpu) != s + throw("result of cpu and gpu differ in single qubit operation. n=$n iteration=$iteration operation=$op") + end + end + function approx(a, b, threshold) + maximum(abs.(a - b)) <= threshold + end -@testset "GPU" begin - CUDA.allowscalar(false) # make sure we are using GPU kernels and not iterating on indices - @test begin - p = random_pauli(3) - p_gpu = to_gpu(p) - typeof(p_gpu.xz) <: CUDA.CuArray # todo this is a bad test because it depends on representation of data in QuantumClifford. Change later... - end - @test begin - s = random_stabilizer(3) - s_gpu = to_gpu(s) - typeof(tab(s_gpu).xzs) <: CUDA.CuArray # todo this is a bad test because it depends on representation of data in QuantumClifford. Change later... - end + @testset "GPU" begin + CUDA.allowscalar(false) # make sure we are using GPU kernels and not iterating on indices - @test begin - for n in [2, 4, 8, 100, 500] - s = random_stabilizer(n) + @test begin + p = random_pauli(3) + p_gpu = to_gpu(p) + typeof(p_gpu.xz) <: CUDA.CuArray # todo this is a bad test because it depends on representation of data in QuantumClifford. Change later... + end + @test begin + s = random_stabilizer(3) s_gpu = to_gpu(s) - for iteration in 1:100 - apply_single_qubit_and_compare(n, s, s_gpu) - end + typeof(tab(s_gpu).xzs) <: CUDA.CuArray # todo this is a bad test because it depends on representation of data in QuantumClifford. Change later... end - true - end - @test begin - for n in [2, 4, 8, 100, 500] - s = random_stabilizer(n) - s_gpu = to_gpu(s) - for iteration in 1:100 - apply_single_or_double_qubit_and_compare(n, s, s_gpu) + @test begin + for n in [2, 4, 8, 100, 500] + s = random_stabilizer(n) + s_gpu = to_gpu(s) + for iteration in 1:100 + apply_single_qubit_and_compare(n, s, s_gpu) + end end + true end - true - end - @test begin - # todo test MRZ and other random gates statistically - circuit = [sHadamard(2), sHadamard(5), sCNOT(1, 2), sCNOT(2, 5), sMZ(1), sMZ(2), sMZ(4), sMZ(5)] - ccircuit = if eltype(circuit) <: QuantumClifford.CompactifiedGate - circuit - else - compactify_circuit(circuit) + @test begin + for n in [2, 4, 8, 100, 500] + s = random_stabilizer(n) + s_gpu = to_gpu(s) + for iteration in 1:100 + apply_single_or_double_qubit_and_compare(n, s, s_gpu) + end + end + true end - for func in [identity, fastcolumn, fastrow] - frames = QuantumClifford._create_pauliframe(ccircuit; trajectories=10) - cpu_frames = func(to_cpu(frames)) - gpu_frames = func(to_gpu(frames)) - cpu_result = pftrajectories(cpu_frames, ccircuit) - gpu_result = pftrajectories(gpu_frames, ccircuit) - check = (cpu_result.frame == to_cpu(gpu_result.frame)) && (cpu_result.measurements == to_cpu(gpu_result.measurements)) - if !check - throw("pftrajectories produce wrong result after applying $func") + @test begin + # todo test MRZ and other random gates statistically + circuit = [sHadamard(2), sHadamard(5), sCNOT(1, 2), sCNOT(2, 5), sMZ(1), sMZ(2), sMZ(4), sMZ(5)] + ccircuit = if eltype(circuit) <: QuantumClifford.CompactifiedGate + circuit + else + compactify_circuit(circuit) end + + for func in [identity, fastcolumn, fastrow] + frames = QuantumClifford._create_pauliframe(ccircuit; trajectories=10) + cpu_frames = func(to_cpu(frames)) + gpu_frames = func(to_gpu(frames)) + cpu_result = pftrajectories(cpu_frames, ccircuit) + gpu_result = pftrajectories(gpu_frames, ccircuit) + check = (cpu_result.frame == to_cpu(gpu_result.frame)) && (cpu_result.measurements == to_cpu(gpu_result.measurements)) + if !check + throw("pftrajectories produce wrong result after applying $func") + end + end + true end - true - end - @test begin - # test fastrow - for n in [2, 4, 8, 100, 500] - s = fastrow(random_stabilizer(n)) - s_gpu = fastrow(to_gpu(s)) - for iteration in 1:100 - # how to check if after each iteration we are still fastrow - apply_single_or_double_qubit_and_compare(n, s, s_gpu) + @test begin + # test fastrow + for n in [2, 4, 8, 100, 500] + s = fastrow(random_stabilizer(n)) + s_gpu = fastrow(to_gpu(s)) + for iteration in 1:100 + # how to check if after each iteration we are still fastrow + apply_single_or_double_qubit_and_compare(n, s, s_gpu) + end end + true end - true - end - @test begin - # test fastcolumn - for n in [2, 4, 8, 100, 500] - s = fastcolumn(random_stabilizer(n)) - s_gpu = fastcolumn(to_gpu(s)) - for iteration in 1:100 - # how to check if after each iteration we are still fastrow - apply_single_or_double_qubit_and_compare(n, s, s_gpu) + @test begin + # test fastcolumn + for n in [2, 4, 8, 100, 500] + s = fastcolumn(random_stabilizer(n)) + s_gpu = fastcolumn(to_gpu(s)) + for iteration in 1:100 + # how to check if after each iteration we are still fastrow + apply_single_or_double_qubit_and_compare(n, s, s_gpu) + end end + true end - true - end - @test begin - # test applynoise - N = 4 - trajectories = 10000 - f = PauliFrame(trajectories, N, N); - f = to_gpu(f) - p = 0.1 - measurements = pftrajectories(f, [ - sMZ(1, 1), - sHadamard(2), - sMZ(2, 2), - NoiseOp(UnbiasedUncorrelatedNoise(p), [3, 4]), - sMZ(3, 3), - sHadamard(4), - sMZ(4, 4) - ]).measurements - avg_result = to_cpu(sum(measurements, dims=1) / trajectories) - error_threshold = 0.02 - approx(vec(avg_result), [0, .5, 2p/3, .5], error_threshold) + @test begin + # test applynoise + N = 4 + trajectories = 10000 + f = PauliFrame(trajectories, N, N); + f = to_gpu(f) + p = 0.1 + measurements = pftrajectories(f, [ + sMZ(1, 1), + sHadamard(2), + sMZ(2, 2), + NoiseOp(UnbiasedUncorrelatedNoise(p), [3, 4]), + sMZ(3, 3), + sHadamard(4), + sMZ(4, 4) + ]).measurements + avg_result = to_cpu(sum(measurements, dims=1) / trajectories) + error_threshold = 0.02 + approx(vec(avg_result), [0, .5, 2p/3, .5], error_threshold) + end end end diff --git a/test/test_graphs.jl b/test/test_graphs.jl index ce7e0cb20..3f21b7583 100644 --- a/test/test_graphs.jl +++ b/test/test_graphs.jl @@ -1,10 +1,9 @@ -using Graphs -using Random -using QuantumClifford +@testitem "Graph states" begin + using Graphs + using Random -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -@testset "Graph states" begin for n in [1,test_sizes...] s = random_stabilizer(n) g, h_idx, ip_idx, z_idx = graphstate(s) diff --git a/test/test_hash.jl b/test/test_hash.jl index bbad9757e..621e7b42f 100644 --- a/test/test_hash.jl +++ b/test/test_hash.jl @@ -1,6 +1,4 @@ -using QuantumClifford - -@testset "Hashing" begin +@testitem "Hashing" begin @test hash(P"X") == hash(P"X") @test hash(S"X") == hash(S"X") @test hash(C"X Z") == hash(C"X Z") diff --git a/test/test_inner.jl b/test/test_inner.jl index a9b6dd9a2..fb61f3f71 100644 --- a/test/test_inner.jl +++ b/test/test_inner.jl @@ -1,9 +1,7 @@ -using LinearAlgebra -using QuantumClifford +@testitem "Inner product between stabilizer states" begin + using LinearAlgebra + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. - -@testset "Inner product between stabilizer states" begin for n in rand(test_sizes) s1 = one(Stabilizer, n, basis=:X) s2 = one(MixedDestabilizer, n, n) @@ -14,11 +12,11 @@ test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off @test logdot(s3,s4)==logdot(c*s3,c*s4) end sa = S" XX - ZZ" + ZZ" sb = S" XZ - ZX" + ZX" sc = S" XX - -ZZ" + -ZZ" @test isnothing(logdot(sa,sb)) @test isnothing(logdot(S"Y",S"-Y")) @test logdot(S"Z",S"X") == 1 diff --git a/test/test_jet.jl b/test/test_jet.jl index 8b4f0c330..ca8e8d9b1 100644 --- a/test/test_jet.jl +++ b/test/test_jet.jl @@ -1,5 +1,4 @@ -using Test -using QuantumClifford +@testitem "JET checks" tags=[:jet] begin using JET using ArrayInterface using Static @@ -23,7 +22,6 @@ function (::MayThrowIsOk)(report_type::Type{<:InferenceErrorReport}, @nospeciali BasicPass()(report_type, args...) end -@testset "JET checks" begin rep = report_package("QuantumClifford"; report_pass=MayThrowIsOk(), ignored_modules=( diff --git a/test/test_memorylayout.jl b/test/test_memorylayout.jl index e0319ff35..1c6d1aeb0 100644 --- a/test/test_memorylayout.jl +++ b/test/test_memorylayout.jl @@ -1,8 +1,5 @@ -using Random -using QuantumClifford -using Test - -@testset "Column-fast vs row-fast operations" begin +@testitem "Column-fast vs row-fast operations" begin + using Random for n in [3, 300] for T in (Stabilizer, Destabilizer, MixedStabilizer, MixedDestabilizer) s = T(random_stabilizer(n,n)) diff --git a/test/test_mul_leftright.jl b/test/test_mul_leftright.jl index 1d7c14e45..1883d6789 100644 --- a/test/test_mul_leftright.jl +++ b/test/test_mul_leftright.jl @@ -1,39 +1,38 @@ -using QuantumClifford -using QuantumClifford: mul_left!, mul_right! +@testitem "Mul leftright" begin + @testset "Pauli string multiplication" begin + using QuantumClifford: mul_left!, mul_right! + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -using Test -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. - -@testset "Pauli string multiplication" begin for n in test_sizes - for _ in 1:20 - p1 = random_pauli(n) - p2 = random_pauli(n) - s = random_stabilizer(n) - i = rand(1:n) - @test p1*p2 == mul_left!(copy(p2), p1) - @test p1*p2 == mul_right!(copy(p1), p2) - @test mul_left!(copy(p2), p1) == (-1)^comm(p1,p2) * mul_right!(copy(p2), p1) - @test mul_left!(copy(p2), s[i]) == mul_left!(copy(p2), s, i) == s[i]*p2 - @test mul_right!(copy(p2), s[i]) == mul_right!(copy(p2), s, i) == p2*s[i] - @test mul_left!(copy(s), p2)[i] == p2*s[i] - @test mul_right!(copy(s), p2)[i] == s[i]*p2 - end + for _ in 1:20 + p1 = random_pauli(n) + p2 = random_pauli(n) + s = random_stabilizer(n) + i = rand(1:n) + @test p1*p2 == mul_left!(copy(p2), p1) + @test p1*p2 == mul_right!(copy(p1), p2) + @test mul_left!(copy(p2), p1) == (-1)^comm(p1,p2) * mul_right!(copy(p2), p1) + @test mul_left!(copy(p2), s[i]) == mul_left!(copy(p2), s, i) == s[i]*p2 + @test mul_right!(copy(p2), s[i]) == mul_right!(copy(p2), s, i) == p2*s[i] + @test mul_left!(copy(s), p2)[i] == p2*s[i] + @test mul_right!(copy(s), p2)[i] == s[i]*p2 + end end -end + end -# test for #320 -@testset "verify SIMD implementation" begin + # test for #320 + @testset "verify SIMD implementation" begin for i in 1:10, - n in 1:30, - T in [UInt8, UInt16, UInt32, UInt64] - a = rand(T, n) - b = rand(T, n) - c1,c2 = QuantumClifford.mul_ordered!(copy(a),copy(b)) - n1,n2 = QuantumClifford._mul_ordered_nonvec!(copy(a),copy(b)) - np = ((n1 ⊻ (n2<<1))&0x3) - cp = ((c1 ⊻ (c2<<1))&0x3) - @test np==cp + n in 1:30, + T in [UInt8, UInt16, UInt32, UInt64] + a = rand(T, n) + b = rand(T, n) + c1,c2 = QuantumClifford.mul_ordered!(copy(a),copy(b)) + n1,n2 = QuantumClifford._mul_ordered_nonvec!(copy(a),copy(b)) + np = ((n1 ⊻ (n2<<1))&0x3) + cp = ((c1 ⊻ (c2<<1))&0x3) + @test np==cp end + end end diff --git a/test/test_noisycircuits.jl b/test/test_noisycircuits.jl index 8ba7a3a60..22a4b09bf 100644 --- a/test/test_noisycircuits.jl +++ b/test/test_noisycircuits.jl @@ -1,284 +1,284 @@ # TODO split in separate files -using Test -using Random -using QuantumClifford +@testitem "Noisy" begin + using Random -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -using QuantumClifford.Experimental.NoisyCircuits -import AbstractAlgebra + using QuantumClifford.Experimental.NoisyCircuits + import AbstractAlgebra -@testset "Noisy Gates" begin - g1 = SparseGate(tId1, [1]) - g2 = SparseGate(tCNOT, [2,3]) - g3 = sCNOT(4,5) - g4 = sHadamard(6) - n = UnbiasedUncorrelatedNoise(1) - ng1 = NoisyGate(g1, n) - ng2 = NoisyGate(g2, n) - ng3 = NoisyGate(g3, n) - ng4 = NoisyGate(g4, n) - ng5 = NoiseOp(n,[7]) - state = ghz(7) - res1, _ = mctrajectory!(copy(state), [ng1,ng2,ng3,ng4,ng5]) - res2, _ = mctrajectory!(copy(state), [g1,g2,g3,g4]) - @test res1 != res2 # has a very small chance of failing - resp = petrajectories(copy(state), [ng1,ng2,ng3,ng4,ng5]) - @test all(values(resp).==0) -end -@testset "Monte Carlo Purification examples" begin - g1 = SparseGate(tCNOT, [1,3]) - g2 = SparseGate(tCNOT, [2,4]) - m = BellMeasurement([sMX(3),sMX(4)]) - good_bell_state = S"XX - ZZ" - canonicalize_rref!(good_bell_state) - v = VerifyOp(good_bell_state,[1,2]) - n = NoiseOpAll(UnbiasedUncorrelatedNoise(0.03)) - init = Register(MixedDestabilizer(good_bell_state⊗good_bell_state)) - with_purification = mctrajectories(init, [n,g1,g2,m,v], trajectories=500) - @test with_purification[failure_stat] > 5 - @test with_purification[false_success_stat] > 10 - @test with_purification[true_success_stat] > 420 - without_purification = mctrajectories(init, [n,v], trajectories=500) - @test get(without_purification,failure_stat,0) == 0 - @test without_purification[false_success_stat] > 10 - @test without_purification[true_success_stat] > 450 - nonoise = mctrajectories(init, [g1,g2,m,v], trajectories=10) - @test get(nonoise,failure_stat,0) == 0 - @test get(nonoise,false_success_stat,0) == 0 - @test nonoise[true_success_stat] == 10 -end - - - -@testset "Perturbative expansion Purification examples" begin - @testset "Comparison to MC" begin - compare(a,b, symbol) = abs(a[symbol]/500-b[symbol]) / (a[symbol]/500+b[symbol]+1e-5) < 0.3 + @testset "Noisy Gates" begin + g1 = SparseGate(tId1, [1]) + g2 = SparseGate(tCNOT, [2,3]) + g3 = sCNOT(4,5) + g4 = sHadamard(6) + n = UnbiasedUncorrelatedNoise(1) + ng1 = NoisyGate(g1, n) + ng2 = NoisyGate(g2, n) + ng3 = NoisyGate(g3, n) + ng4 = NoisyGate(g4, n) + ng5 = NoiseOp(n,[7]) + state = ghz(7) + res1, _ = mctrajectory!(copy(state), [ng1,ng2,ng3,ng4,ng5]) + res2, _ = mctrajectory!(copy(state), [g1,g2,g3,g4]) + @test res1 != res2 # has a very small chance of failing + resp = petrajectories(copy(state), [ng1,ng2,ng3,ng4,ng5]) + @test all(values(resp).==0) + end + @testset "Monte Carlo Purification examples" begin g1 = SparseGate(tCNOT, [1,3]) g2 = SparseGate(tCNOT, [2,4]) m = BellMeasurement([sMX(3),sMX(4)]) good_bell_state = S"XX - ZZ" + ZZ" canonicalize_rref!(good_bell_state) v = VerifyOp(good_bell_state,[1,2]) n = NoiseOpAll(UnbiasedUncorrelatedNoise(0.03)) init = Register(MixedDestabilizer(good_bell_state⊗good_bell_state)) - mc = mctrajectories(init, [n,g1,g2,m,v], trajectories=500) - pe = petrajectories(init, [n,g1,g2,m,v]) - @test compare(mc,pe,failure_stat) - @test compare(mc,pe,false_success_stat) - @test compare(mc,pe,true_success_stat) - mc = mctrajectories(init, [n,v], trajectories=500) - pe = petrajectories(init, [n,v]) - @test compare(mc,pe,failure_stat) - @test compare(mc,pe,false_success_stat) - @test compare(mc,pe,true_success_stat) - mc = mctrajectories(init, [g1,g2,m,v], trajectories=500) - pe = petrajectories(init, [g1,g2,m,v]) - @test compare(mc,pe,failure_stat) - @test compare(mc,pe,false_success_stat) - @test compare(mc,pe,true_success_stat) - end - @testset "Symbolic" begin - R, (e,) = AbstractAlgebra.polynomial_ring(AbstractAlgebra.RealField, ["e"]) - unity = R(1); - good_bell_state = Register(MixedDestabilizer(S"XX ZZ")) - initial_state = good_bell_state⊗good_bell_state - g1 = SparseGate(tCNOT, [1,3]) # CNOT between qubit 1 and qubit 3 (both with Alice) - g2 = SparseGate(tCNOT, [2,4]) # CNOT between qubit 2 and qubit 4 (both with Bob) - m = BellMeasurement([sMX(3),sMX(4)]) # Bell measurement on qubit 3 and 4 - v = VerifyOp(good_bell_state,[1,2]) # Verify that qubit 1 and 2 indeed form a good Bell pair - epsilon = e # The X or Y or Z error rate - n = NoiseOpAll(UnbiasedUncorrelatedNoise(3epsilon)) - circuit = [n,g1,g2,m,v] - pe_symbolic = petrajectories(initial_state, circuit, branch_weight=unity) # perturbative expansion - @test pe_symbolic[false_success_stat] == -162.0*e^4 + 162.0*e^3 + -54.0*e^2 + 6.0*e - @test pe_symbolic[failure_stat] == -108.0*e^4 + 108.0*e^3 + -36.0*e^2 + 4.0*e - @test pe_symbolic[true_success_stat] == 27.0*e^4 + -54.0*e^3 + 36.0*e^2 + -10.0*e + 1.0 + with_purification = mctrajectories(init, [n,g1,g2,m,v], trajectories=500) + @test with_purification[failure_stat] > 5 + @test with_purification[false_success_stat] > 10 + @test with_purification[true_success_stat] > 420 + without_purification = mctrajectories(init, [n,v], trajectories=500) + @test get(without_purification,failure_stat,0) == 0 + @test without_purification[false_success_stat] > 10 + @test without_purification[true_success_stat] > 450 + nonoise = mctrajectories(init, [g1,g2,m,v], trajectories=10) + @test get(nonoise,failure_stat,0) == 0 + @test get(nonoise,false_success_stat,0) == 0 + @test nonoise[true_success_stat] == 10 end -end -@testset "Measurements" begin - @testset "BellMeasurements" begin - stateX = S"X" - mX = BellMeasurement([sMX(1)]) - vX = VerifyOp(S"X", [1]) - determinate1 = mctrajectories(Register(MixedDestabilizer(stateX)), [mX,vX], trajectories=10) - @test determinate1[failure_stat] == 0 - @test determinate1[false_success_stat] == 0 - @test determinate1[true_success_stat] == 10 - determinate1_pe = petrajectories(Register(MixedDestabilizer(copy(stateX))), [mX,vX]) - @test determinate1_pe[failure_stat] == 0 - @test determinate1_pe[false_success_stat] == 0 - @test determinate1_pe[true_success_stat] == 1 - stateZ = S"Z" - random1 = mctrajectories(Register(MixedDestabilizer(stateZ)), [mX,vX], trajectories=500) - @test random1[failure_stat] > 200 - @test random1[false_success_stat] == 0 - @test random1[true_success_stat] > 200 - random1_pe = petrajectories(Register(MixedDestabilizer(copy(stateZ))), [mX,vX]) - @test random1_pe[failure_stat] > 0.4 - @test random1_pe[false_success_stat] == 0 - @test random1_pe[true_success_stat] > 0.4 - bell_state = S" XX - ZZ" - m1 = BellMeasurement([sMX(1),sMX(2)]) - determinate2 = mctrajectories(Register(bell_state), [m1], trajectories=10) - @test determinate2[failure_stat] == 0 - @test determinate2[false_success_stat] == 0 - @test determinate2[continue_stat] == 10 - determinate2_pe = petrajectories(Register(copy(bell_state)), [m1]) - @test determinate2_pe[failure_stat] == 0 - @test determinate2_pe[false_success_stat] == 0 - m2 = BellMeasurement([sMX(1),sMZ(2)]) - v = VerifyOp(bell_state, [1,2]) - random2 = mctrajectories(Register(bell_state), [m2,v], trajectories=500) - @test random2[failure_stat]+random2[false_success_stat] == 500 - @test random2[true_success_stat] == 0 - random2_pe = petrajectories(Register(copy(bell_state)), [m2,v]) - @test random2_pe[failure_stat]+random2_pe[false_success_stat] == 1 - @test random2_pe[true_success_stat] == 0 - end - @testset "PauliMeasurements" begin - ghzState = S"XXX - ZZI - IZZ" - m1 = PauliMeasurement(P"ZZI", 1) - v = VerifyOp(ghzState, [1,2,3]) - register1 = Register(ghzState, zeros(Bool, 1)) - determinate1 = mctrajectories(register1, [m1,v], trajectories=10) - @test determinate1[failure_stat] == 0 - @test determinate1[false_success_stat] == 0 - @test determinate1[true_success_stat] == 10 - determinate1_pe = petrajectories(register1, [m1,v]) - @test determinate1_pe[failure_stat] == 0 - @test determinate1_pe[false_success_stat] == 0 - @test determinate1_pe[true_success_stat] == 1 - m2 = PauliMeasurement(P"ZII", 1) - register1 = Register(ghzState, zeros(Bool, 1)) - random1 = mctrajectories(register1, [m2,v], trajectories=50) - @test random1[failure_stat] == 0 - @test random1[false_success_stat] == 50 - @test random1[true_success_stat] == 0 - random1_pe = petrajectories(register1, [m2,v]) - @test random1_pe[failure_stat] == 0 - @test random1_pe[false_success_stat] == 1 - @test random1_pe[true_success_stat] == 0 - m3 = PauliMeasurement(P"XII", 1) - register1 = Register(ghzState, zeros(Bool, 1)) - random2 = mctrajectories(register1, [m3,v], trajectories=50) - @test random2[failure_stat] == 0 - @test random2[false_success_stat] == 50 - @test random2[true_success_stat] == 0 - random2_pe = petrajectories(register1, [m3,v]) - @test random2_pe[failure_stat] == 0 - @test random2_pe[false_success_stat] == 1 - @test random2_pe[true_success_stat] == 0 - end - @testset "Sparse Measurements" begin - ghzState = S"XXX - ZZI - IZZ" - v = VerifyOp(ghzState, [1,2,3]) - #= TODO reintroduce this type of SparseMeasurement (more than one qubit (so not sMZ), but not all qubits (so not DenseMeasurement)) - m1 = SparseMeasurement(P"ZZ", [1,2], 1) - register1 = Register(ghzState, zeros(Bool, 1)) - determinate1 = mctrajectories(register1, [m1,v], trajectories=10) - @test determinate1[failure_stat] == 0 - @test determinate1[false_success_stat] == 0 - @test determinate1[true_success_stat] == 10 - determinate1_pe = petrajectories(register1, [m1,v]) - @test determinate1_pe[failure_stat] == 0 - @test determinate1_pe[false_success_stat] == 0 - @test determinate1_pe[true_success_stat] == 1 - =# - m2 = sMZ(1, 1) - register1 = Register(ghzState, zeros(Bool, 1)) - random1 = mctrajectories(register1, [m2,v], trajectories=50) - @test random1[failure_stat] == 0 - @test random1[false_success_stat] == 50 - @test random1[true_success_stat] == 0 - random1_pe = petrajectories(register1, [m2,v]) - @test random1_pe[failure_stat] == 0 - @test random1_pe[false_success_stat] == 1 - @test random1_pe[true_success_stat] == 0 - m3 = sMX(1, 1) - register1 = Register(ghzState, zeros(Bool, 1)) - random2 = mctrajectories(register1, [m3,v], trajectories=50) - @test random2[failure_stat] == 0 - @test random2[false_success_stat] == 50 - @test random2[true_success_stat] == 0 - random2_pe = petrajectories(register1, [m3,v]) - @test random2_pe[failure_stat] == 0 - @test random2_pe[false_success_stat] == 1 - @test random2_pe[true_success_stat] == 0 + + + + @testset "Perturbative expansion Purification examples" begin + @testset "Comparison to MC" begin + compare(a,b, symbol) = abs(a[symbol]/500-b[symbol]) / (a[symbol]/500+b[symbol]+1e-5) < 0.3 + g1 = SparseGate(tCNOT, [1,3]) + g2 = SparseGate(tCNOT, [2,4]) + m = BellMeasurement([sMX(3),sMX(4)]) + good_bell_state = S"XX + ZZ" + canonicalize_rref!(good_bell_state) + v = VerifyOp(good_bell_state,[1,2]) + n = NoiseOpAll(UnbiasedUncorrelatedNoise(0.03)) + init = Register(MixedDestabilizer(good_bell_state⊗good_bell_state)) + mc = mctrajectories(init, [n,g1,g2,m,v], trajectories=500) + pe = petrajectories(init, [n,g1,g2,m,v]) + @test compare(mc,pe,failure_stat) + @test compare(mc,pe,false_success_stat) + @test compare(mc,pe,true_success_stat) + mc = mctrajectories(init, [n,v], trajectories=500) + pe = petrajectories(init, [n,v]) + @test compare(mc,pe,failure_stat) + @test compare(mc,pe,false_success_stat) + @test compare(mc,pe,true_success_stat) + mc = mctrajectories(init, [g1,g2,m,v], trajectories=500) + pe = petrajectories(init, [g1,g2,m,v]) + @test compare(mc,pe,failure_stat) + @test compare(mc,pe,false_success_stat) + @test compare(mc,pe,true_success_stat) + end + @testset "Symbolic" begin + R, (e,) = AbstractAlgebra.polynomial_ring(AbstractAlgebra.RealField, ["e"]) + unity = R(1); + good_bell_state = Register(MixedDestabilizer(S"XX ZZ")) + initial_state = good_bell_state⊗good_bell_state + g1 = SparseGate(tCNOT, [1,3]) # CNOT between qubit 1 and qubit 3 (both with Alice) + g2 = SparseGate(tCNOT, [2,4]) # CNOT between qubit 2 and qubit 4 (both with Bob) + m = BellMeasurement([sMX(3),sMX(4)]) # Bell measurement on qubit 3 and 4 + v = VerifyOp(good_bell_state,[1,2]) # Verify that qubit 1 and 2 indeed form a good Bell pair + epsilon = e # The X or Y or Z error rate + n = NoiseOpAll(UnbiasedUncorrelatedNoise(3epsilon)) + circuit = [n,g1,g2,m,v] + pe_symbolic = petrajectories(initial_state, circuit, branch_weight=unity) # perturbative expansion + @test pe_symbolic[false_success_stat] == -162.0*e^4 + 162.0*e^3 + -54.0*e^2 + 6.0*e + @test pe_symbolic[failure_stat] == -108.0*e^4 + 108.0*e^3 + -36.0*e^2 + 4.0*e + @test pe_symbolic[true_success_stat] == 27.0*e^4 + -54.0*e^3 + 36.0*e^2 + -10.0*e + 1.0 + end end - @testset "Conforming to the project! interface" begin - state = Register(MixedDestabilizer(S"ZZ"), zeros(Bool, 1)) - meas = PauliMeasurement(P"ZI", 1) - state, flag = applywstatus!(state, meas) - @test state.stab.rank == 2 - tab(state.stab).phases .= 0 - @test stabilizerview(state.stab) == S"ZZ - ZI" + @testset "Measurements" begin + @testset "BellMeasurements" begin + stateX = S"X" + mX = BellMeasurement([sMX(1)]) + vX = VerifyOp(S"X", [1]) + determinate1 = mctrajectories(Register(MixedDestabilizer(stateX)), [mX,vX], trajectories=10) + @test determinate1[failure_stat] == 0 + @test determinate1[false_success_stat] == 0 + @test determinate1[true_success_stat] == 10 + determinate1_pe = petrajectories(Register(MixedDestabilizer(copy(stateX))), [mX,vX]) + @test determinate1_pe[failure_stat] == 0 + @test determinate1_pe[false_success_stat] == 0 + @test determinate1_pe[true_success_stat] == 1 + stateZ = S"Z" + random1 = mctrajectories(Register(MixedDestabilizer(stateZ)), [mX,vX], trajectories=500) + @test random1[failure_stat] > 200 + @test random1[false_success_stat] == 0 + @test random1[true_success_stat] > 200 + random1_pe = petrajectories(Register(MixedDestabilizer(copy(stateZ))), [mX,vX]) + @test random1_pe[failure_stat] > 0.4 + @test random1_pe[false_success_stat] == 0 + @test random1_pe[true_success_stat] > 0.4 + bell_state = S" XX + ZZ" + m1 = BellMeasurement([sMX(1),sMX(2)]) + determinate2 = mctrajectories(Register(bell_state), [m1], trajectories=10) + @test determinate2[failure_stat] == 0 + @test determinate2[false_success_stat] == 0 + @test determinate2[continue_stat] == 10 + determinate2_pe = petrajectories(Register(copy(bell_state)), [m1]) + @test determinate2_pe[failure_stat] == 0 + @test determinate2_pe[false_success_stat] == 0 + m2 = BellMeasurement([sMX(1),sMZ(2)]) + v = VerifyOp(bell_state, [1,2]) + random2 = mctrajectories(Register(bell_state), [m2,v], trajectories=500) + @test random2[failure_stat]+random2[false_success_stat] == 500 + @test random2[true_success_stat] == 0 + random2_pe = petrajectories(Register(copy(bell_state)), [m2,v]) + @test random2_pe[failure_stat]+random2_pe[false_success_stat] == 1 + @test random2_pe[true_success_stat] == 0 + end + @testset "PauliMeasurements" begin + ghzState = S"XXX + ZZI + IZZ" + m1 = PauliMeasurement(P"ZZI", 1) + v = VerifyOp(ghzState, [1,2,3]) + register1 = Register(ghzState, zeros(Bool, 1)) + determinate1 = mctrajectories(register1, [m1,v], trajectories=10) + @test determinate1[failure_stat] == 0 + @test determinate1[false_success_stat] == 0 + @test determinate1[true_success_stat] == 10 + determinate1_pe = petrajectories(register1, [m1,v]) + @test determinate1_pe[failure_stat] == 0 + @test determinate1_pe[false_success_stat] == 0 + @test determinate1_pe[true_success_stat] == 1 + m2 = PauliMeasurement(P"ZII", 1) + register1 = Register(ghzState, zeros(Bool, 1)) + random1 = mctrajectories(register1, [m2,v], trajectories=50) + @test random1[failure_stat] == 0 + @test random1[false_success_stat] == 50 + @test random1[true_success_stat] == 0 + random1_pe = petrajectories(register1, [m2,v]) + @test random1_pe[failure_stat] == 0 + @test random1_pe[false_success_stat] == 1 + @test random1_pe[true_success_stat] == 0 + m3 = PauliMeasurement(P"XII", 1) + register1 = Register(ghzState, zeros(Bool, 1)) + random2 = mctrajectories(register1, [m3,v], trajectories=50) + @test random2[failure_stat] == 0 + @test random2[false_success_stat] == 50 + @test random2[true_success_stat] == 0 + random2_pe = petrajectories(register1, [m3,v]) + @test random2_pe[failure_stat] == 0 + @test random2_pe[false_success_stat] == 1 + @test random2_pe[true_success_stat] == 0 + end + @testset "Sparse Measurements" begin + ghzState = S"XXX + ZZI + IZZ" + v = VerifyOp(ghzState, [1,2,3]) + #= TODO reintroduce this type of SparseMeasurement (more than one qubit (so not sMZ), but not all qubits (so not DenseMeasurement)) + m1 = SparseMeasurement(P"ZZ", [1,2], 1) + register1 = Register(ghzState, zeros(Bool, 1)) + determinate1 = mctrajectories(register1, [m1,v], trajectories=10) + @test determinate1[failure_stat] == 0 + @test determinate1[false_success_stat] == 0 + @test determinate1[true_success_stat] == 10 + determinate1_pe = petrajectories(register1, [m1,v]) + @test determinate1_pe[failure_stat] == 0 + @test determinate1_pe[false_success_stat] == 0 + @test determinate1_pe[true_success_stat] == 1 + =# + m2 = sMZ(1, 1) + register1 = Register(ghzState, zeros(Bool, 1)) + random1 = mctrajectories(register1, [m2,v], trajectories=50) + @test random1[failure_stat] == 0 + @test random1[false_success_stat] == 50 + @test random1[true_success_stat] == 0 + random1_pe = petrajectories(register1, [m2,v]) + @test random1_pe[failure_stat] == 0 + @test random1_pe[false_success_stat] == 1 + @test random1_pe[true_success_stat] == 0 + m3 = sMX(1, 1) + register1 = Register(ghzState, zeros(Bool, 1)) + random2 = mctrajectories(register1, [m3,v], trajectories=50) + @test random2[failure_stat] == 0 + @test random2[false_success_stat] == 50 + @test random2[true_success_stat] == 0 + random2_pe = petrajectories(register1, [m3,v]) + @test random2_pe[failure_stat] == 0 + @test random2_pe[false_success_stat] == 1 + @test random2_pe[true_success_stat] == 0 + end + @testset "Conforming to the project! interface" begin + state = Register(MixedDestabilizer(S"ZZ"), zeros(Bool, 1)) + meas = PauliMeasurement(P"ZI", 1) + state, flag = applywstatus!(state, meas) + @test state.stab.rank == 2 + tab(state.stab).phases .= 0 + @test stabilizerview(state.stab) == S"ZZ + ZI" + end end -end -@testset "Classical Bits" begin - @testset "DecisionGate" begin - X_error = CliffordOperator([P"X", P"-Z"]) - # testing single digit return value from decision function - for s in [S"Z", S"-Z", S"X", S"-X", S"Y", S"-Y"] + @testset "Classical Bits" begin + @testset "DecisionGate" begin + X_error = CliffordOperator([P"X", P"-Z"]) + # testing single digit return value from decision function + for s in [S"Z", S"-Z", S"X", S"-X", S"Y", S"-Y"] + r = Register(s, [false]) + applywstatus!(r, PauliMeasurement(P"Z", 1)) + correctiveGate = SparseGate(X_error, [1]) + decisionFunction = syndrome -> syndrome[1] ? 1 : nothing + applywstatus!(r, DecisionGate([correctiveGate], decisionFunction)) + @test stabilizerview(r) == S"Z" + end + + # testing an array return from decision function + expectedFinalState = S"ZI + IZ" + s = QuantumClifford.bell() r = Register(s, [false]) - applywstatus!(r, PauliMeasurement(P"Z", 1)) - correctiveGate = SparseGate(X_error, [1]) - decisionFunction = syndrome -> syndrome[1] ? 1 : nothing - applywstatus!(r, DecisionGate([correctiveGate], decisionFunction)) - @test stabilizerview(r) == S"Z" - end + applywstatus!(r, PauliMeasurement(P"ZI", 1)) + correctiveGates = [SparseGate(X_error, [1]), SparseGate(X_error, [2])] + decisionFunction = syndrome -> syndrome[1] ? [1,2] : nothing + applywstatus!(r, DecisionGate(correctiveGates, decisionFunction)) + canonicalize!(quantumstate(r)) + @test stabilizerview(r) == expectedFinalState - # testing an array return from decision function - expectedFinalState = S"ZI - IZ" - s = QuantumClifford.bell() - r = Register(s, [false]) - applywstatus!(r, PauliMeasurement(P"ZI", 1)) - correctiveGates = [SparseGate(X_error, [1]), SparseGate(X_error, [2])] - decisionFunction = syndrome -> syndrome[1] ? [1,2] : nothing - applywstatus!(r, DecisionGate(correctiveGates, decisionFunction)) - canonicalize!(quantumstate(r)) - @test stabilizerview(r) == expectedFinalState + s = QuantumClifford.bell((false, true)) # |01>+|10> + r = Register(s, [false]) + applywstatus!(r, sMZ(1, 1)) + # we use the same corrective gates, with a different decision function + decisionFunction = syndrome -> syndrome[1] ? [1] : 2 # both [1] and 1 should work + applywstatus!(r, DecisionGate(correctiveGates, decisionFunction)) + canonicalize!(quantumstate(r)) + @test stabilizerview(r) == expectedFinalState + end + @testset "ConditionalGate" begin + id_op = CliffordOperator([P"X", P"Z"]) + X_error = CliffordOperator([P"X", P"-Z"]) - s = QuantumClifford.bell((false, true)) # |01>+|10> - r = Register(s, [false]) - applywstatus!(r, sMZ(1, 1)) - # we use the same corrective gates, with a different decision function - decisionFunction = syndrome -> syndrome[1] ? [1] : 2 # both [1] and 1 should work - applywstatus!(r, DecisionGate(correctiveGates, decisionFunction)) - canonicalize!(quantumstate(r)) - @test stabilizerview(r) == expectedFinalState - end - @testset "ConditionalGate" begin - id_op = CliffordOperator([P"X", P"Z"]) - X_error = CliffordOperator([P"X", P"-Z"]) + for s in [S"Z", S"-Z", S"X", S"-X", S"Y", S"-Y"] + r = Register(s, [false]) + applywstatus!(r, PauliMeasurement(P"Z", 1)) + correctiveGate = SparseGate(X_error, [1]) + identityGate = SparseGate(id_op, [1]) + applywstatus!(r, ConditionalGate(correctiveGate, identityGate, 1)) + @test stabilizerview(r) == S"Z" + end - for s in [S"Z", S"-Z", S"X", S"-X", S"Y", S"-Y"] + expectedFinalState = S"ZI + IZ" + s = QuantumClifford.bell((false, true)) r = Register(s, [false]) - applywstatus!(r, PauliMeasurement(P"Z", 1)) - correctiveGate = SparseGate(X_error, [1]) - identityGate = SparseGate(id_op, [1]) - applywstatus!(r, ConditionalGate(correctiveGate, identityGate, 1)) - @test stabilizerview(r) == S"Z" + applywstatus!(r, PauliMeasurement(P"ZI", 1)) + correctiveGate1 = SparseGate(X_error, [1]) + correctiveGate2 = SparseGate(X_error, [2]) + applywstatus!(r, ConditionalGate(correctiveGate1, correctiveGate2, 1)) + canonicalize!(quantumstate(r)) + @test stabilizerview(r) == expectedFinalState end - - expectedFinalState = S"ZI - IZ" - s = QuantumClifford.bell((false, true)) - r = Register(s, [false]) - applywstatus!(r, PauliMeasurement(P"ZI", 1)) - correctiveGate1 = SparseGate(X_error, [1]) - correctiveGate2 = SparseGate(X_error, [2]) - applywstatus!(r, ConditionalGate(correctiveGate1, correctiveGate2, 1)) - canonicalize!(quantumstate(r)) - @test stabilizerview(r) == expectedFinalState end end diff --git a/test/test_nonclifford.jl b/test/test_nonclifford.jl index 292aaac8d..0bf14682e 100644 --- a/test/test_nonclifford.jl +++ b/test/test_nonclifford.jl @@ -1,46 +1,44 @@ -using QuantumClifford -using QuantumClifford: StabMixture, rowdecompose, PauliChannel, mul_left!, mul_right! -using Test -using InteractiveUtils -using Random - -## - -@testset "Pauli decomposition into destabilizers" begin - for n in [1,2,63,64,65,66,300] - p = random_pauli(n; nophase=true) - s = random_destabilizer(n) - phase, b, c = rowdecompose(p,s) - p0 = zero(p) - for (i,f) in pairs(b) - f && mul_right!(p0, destabilizerview(s), i) +@testitem "NonClifford" begin + using QuantumClifford: StabMixture, rowdecompose, PauliChannel, mul_left!, mul_right! + using InteractiveUtils + using Random + + @testset "Pauli decomposition into destabilizers" begin + for n in [1,2,63,64,65,66,300] + p = random_pauli(n; nophase=true) + s = random_destabilizer(n) + phase, b, c = rowdecompose(p,s) + p0 = zero(p) + for (i,f) in pairs(b) + f && mul_right!(p0, destabilizerview(s), i) + end + for (i,f) in pairs(c) + f && mul_right!(p0, stabilizerview(s), i) + end + @test (im)^phase*p0 == p end - for (i,f) in pairs(c) - f && mul_right!(p0, stabilizerview(s), i) - end - @test (im)^phase*p0 == p end -end -## + ## -@testset "PauliChannel T gate ^4 = Id" begin - tgate = pcT - state = StabMixture(S"X") + @testset "PauliChannel T gate ^4 = Id" begin + tgate = pcT + state = StabMixture(S"X") - apply!(state, tgate) - apply!(state, tgate) - apply!(state, tgate) - apply!(state, tgate) + apply!(state, tgate) + apply!(state, tgate) + apply!(state, tgate) + apply!(state, tgate) - @test state.destabweights |> values |> collect == [1] - @test state.destabweights |> keys |> collect == [([1],[1])] -end + @test isapprox(state.destabweights |> values |> collect, [1]) + @test state.destabweights |> keys |> collect == [([1],[1])] + end -## + ## -@test_throws ArgumentError StabMixture(S"XX") -@test_throws ArgumentError PauliChannel(((P"X", P"Z"), (P"X", P"ZZ")), (1,2)) -@test_throws ArgumentError PauliChannel(((P"X", P"Z"), (P"X", P"Z")), (1,)) -@test_throws ArgumentError UnitaryPauliChannel((P"X", P"ZZ"), (1,2)) -@test_throws ArgumentError UnitaryPauliChannel((P"X", P"Z"), (1,)) + @test_throws ArgumentError StabMixture(S"XX") + @test_throws ArgumentError PauliChannel(((P"X", P"Z"), (P"X", P"ZZ")), (1,2)) + @test_throws ArgumentError PauliChannel(((P"X", P"Z"), (P"X", P"Z")), (1,)) + @test_throws ArgumentError UnitaryPauliChannel((P"X", P"ZZ"), (1,2)) + @test_throws ArgumentError UnitaryPauliChannel((P"X", P"Z"), (1,)) +end diff --git a/test/test_pauliframe.jl b/test/test_pauliframe.jl index 0669f2ac2..cc335934c 100644 --- a/test/test_pauliframe.jl +++ b/test/test_pauliframe.jl @@ -1,92 +1,91 @@ -using QuantumClifford -using Test - -@testset "syndrome measurement of 3 qubit repetition code" begin - circuit = [ - sX(1), - sCNOT(1,2), sCNOT(1,3), # encode - PauliError(2,0.75), # error - sCNOT(1,4), sCNOT(2,4), sCNOT(2,5), sCNOT(3,5), sMZ(4,1), sMZ(5,2) # syndrome measurement - ] - frame = PauliFrame(100, 5, 2) - frame = pftrajectories(frame, circuit) - frame1 = pftrajectories(circuit; trajectories=100, threads=false) - frame2 = pftrajectories(circuit; trajectories=100, threads=true) - # If the x component is set on the second qubit, then the fourth and fifth qubits should also have it set - for f in [frame, frame1, frame2] - m = pfmeasurements(f) - for (i, row) in enumerate(f.frame) - if row[2] == (true, false) - @test row[4][1] && row[5][1] && m[i,1] && m[i,2] +@testitem "Pauli frame" begin + @testset "syndrome measurement of 3 qubit repetition code" begin + circuit = [ + sX(1), + sCNOT(1,2), sCNOT(1,3), # encode + PauliError(2,0.75), # error + sCNOT(1,4), sCNOT(2,4), sCNOT(2,5), sCNOT(3,5), sMZ(4,1), sMZ(5,2) # syndrome measurement + ] + frame = PauliFrame(100, 5, 2) + frame = pftrajectories(frame, circuit) + frame1 = pftrajectories(circuit; trajectories=100, threads=false) + frame2 = pftrajectories(circuit; trajectories=100, threads=true) + # If the x component is set on the second qubit, then the fourth and fifth qubits should also have it set + for f in [frame, frame1, frame2] + m = pfmeasurements(f) + for (i, row) in enumerate(f.frame) + if row[2] == (true, false) + @test row[4][1] && row[5][1] && m[i,1] && m[i,2] + end end end end -end -@testset "GHZ correlations" begin - ghz_circuit = [ - sHadamard(1), sCNOT(1,2), sCNOT(1,3), # prepare a GHZ state - sMZ(1,1), sMZ(2,2), sMZ(3,3) # measure each qubit - ] - n = 10^6 - frame = PauliFrame(n, 3, 3) - f = pftrajectories(frame, ghz_circuit) - m = pfmeasurements(f) - frame1 = pftrajectories(ghz_circuit; trajectories=n, threads=false) - m1 = pfmeasurements(frame1) - frame2 = pftrajectories(ghz_circuit; trajectories=n, threads=true) - m2 = pfmeasurements(frame2) - for _m in [m, m1, m2] - rowtotal_1s = sum(m, dims=2)[:,1] - @test all(rowtotal_1s .% 3 .== 0) - fractotal_1s = sum(rowtotal_1s)/3 / n - @test (fractotal_1s > 0.49) && (fractotal_1s < 0.51) + @testset "GHZ correlations" begin + ghz_circuit = [ + sHadamard(1), sCNOT(1,2), sCNOT(1,3), # prepare a GHZ state + sMZ(1,1), sMZ(2,2), sMZ(3,3) # measure each qubit + ] + n = 10^6 + frame = PauliFrame(n, 3, 3) + f = pftrajectories(frame, ghz_circuit) + m = pfmeasurements(f) + frame1 = pftrajectories(ghz_circuit; trajectories=n, threads=false) + m1 = pfmeasurements(frame1) + frame2 = pftrajectories(ghz_circuit; trajectories=n, threads=true) + m2 = pfmeasurements(frame2) + for _m in [m, m1, m2] + rowtotal_1s = sum(m, dims=2)[:,1] + @test all(rowtotal_1s .% 3 .== 0) + fractotal_1s = sum(rowtotal_1s)/3 / n + @test (fractotal_1s > 0.49) && (fractotal_1s < 0.51) + end end -end -@testset "sMZ vs sMRZ - mctrajectories vs pftrajectories" begin - n = 2000 - state = Register(one(MixedDestabilizer, 3), 6) - frame = PauliFrame(n, 3, 6) + @testset "sMZ vs sMRZ - mctrajectories vs pftrajectories" begin + n = 2000 + state = Register(one(MixedDestabilizer, 3), 6) + frame = PauliFrame(n, 3, 6) - ghz_circuit1 = [ - sHadamard(1), sCNOT(1,2), sCNOT(1,3), # prepare a GHZ state - sMZ(1,1), sMZ(2,2), sMZ(3,3), # measure each qubit - sMZ(1,4), sMZ(2,5), sMZ(3,6) # measure each qubit again - ] - for m in [ - stack([bitview(mctrajectory!(copy(state), ghz_circuit1)[1]) for i in 1:n], dims=1), - pfmeasurements(pftrajectories(copy(frame), ghz_circuit1)), - pfmeasurements(pftrajectories(ghz_circuit1;trajectories=n,threads=false)), - pfmeasurements(pftrajectories(ghz_circuit1;trajectories=n,threads=true)), - ] - @test all(0.25 .< sum(m, dims=1)./n .< 0.75) - end + ghz_circuit1 = [ + sHadamard(1), sCNOT(1,2), sCNOT(1,3), # prepare a GHZ state + sMZ(1,1), sMZ(2,2), sMZ(3,3), # measure each qubit + sMZ(1,4), sMZ(2,5), sMZ(3,6) # measure each qubit again + ] + for m in [ + stack([bitview(mctrajectory!(copy(state), ghz_circuit1)[1]) for i in 1:n], dims=1), + pfmeasurements(pftrajectories(copy(frame), ghz_circuit1)), + pfmeasurements(pftrajectories(ghz_circuit1;trajectories=n,threads=false)), + pfmeasurements(pftrajectories(ghz_circuit1;trajectories=n,threads=true)), + ] + @test all(0.25 .< sum(m, dims=1)./n .< 0.75) + end - ghz_circuit2 = [ - sHadamard(1), sCNOT(1,2), sCNOT(1,3), # prepare a GHZ state - sMRZ(1,1), sMZ(2,2), sMZ(3,3), # measure and reset each qubit - sMZ(1,4), sMZ(2,5), sMZ(3,6) # measure each qubit again - ] - for m in [ - stack([bitview(mctrajectory!(copy(state), ghz_circuit2)[1]) for i in 1:n], dims=1), - pfmeasurements(pftrajectories(copy(frame), ghz_circuit2)), - pfmeasurements(pftrajectories(ghz_circuit2;trajectories=n,threads=false)), - pfmeasurements(pftrajectories(ghz_circuit2;trajectories=n,threads=true)), - ] - @test all(0.25.*[1 1 1 0 1 1] .<= sum(m, dims=1)./n .<= 0.75.*[1 1 1 0 1 1]) - end + ghz_circuit2 = [ + sHadamard(1), sCNOT(1,2), sCNOT(1,3), # prepare a GHZ state + sMRZ(1,1), sMZ(2,2), sMZ(3,3), # measure and reset each qubit + sMZ(1,4), sMZ(2,5), sMZ(3,6) # measure each qubit again + ] + for m in [ + stack([bitview(mctrajectory!(copy(state), ghz_circuit2)[1]) for i in 1:n], dims=1), + pfmeasurements(pftrajectories(copy(frame), ghz_circuit2)), + pfmeasurements(pftrajectories(ghz_circuit2;trajectories=n,threads=false)), + pfmeasurements(pftrajectories(ghz_circuit2;trajectories=n,threads=true)), + ] + @test all(0.25.*[1 1 1 0 1 1] .<= sum(m, dims=1)./n .<= 0.75.*[1 1 1 0 1 1]) + end - noncom_circuit = [ - sHadamard(1), sMRZ(1,1), sX(1), sMZ(1,2), sMRZ(1,3), sMRZ(1,4), sHadamard(1), sMZ(1,5) - ] - ms3 = stack([bitview(mctrajectory!(copy(state), noncom_circuit)[1]) for i in 1:n], dims=1) - @test all(0.25.*[1 4 4 0 1 0] .<= sum(ms3, dims=1)./n .<= 0.75.*[1 2 2 0 1 0]) - for m in [ - pfmeasurements(pftrajectories(copy(frame), noncom_circuit)), - pfmeasurements(pftrajectories(noncom_circuit;trajectories=n,threads=false)), - pfmeasurements(pftrajectories(noncom_circuit;trajectories=n,threads=true)), - ] - @test all(0.25.*[1 0 0 0 1] .<= (sum(m, dims=1)[:,1:5])./n .<= 0.75.*[1 0 0 0 1]) + noncom_circuit = [ + sHadamard(1), sMRZ(1,1), sX(1), sMZ(1,2), sMRZ(1,3), sMRZ(1,4), sHadamard(1), sMZ(1,5) + ] + ms3 = stack([bitview(mctrajectory!(copy(state), noncom_circuit)[1]) for i in 1:n], dims=1) + @test all(0.25.*[1 4 4 0 1 0] .<= sum(ms3, dims=1)./n .<= 0.75.*[1 2 2 0 1 0]) + for m in [ + pfmeasurements(pftrajectories(copy(frame), noncom_circuit)), + pfmeasurements(pftrajectories(noncom_circuit;trajectories=n,threads=false)), + pfmeasurements(pftrajectories(noncom_circuit;trajectories=n,threads=true)), + ] + @test all(0.25.*[1 0 0 0 1] .<= (sum(m, dims=1)[:,1:5])./n .<= 0.75.*[1 0 0 0 1]) + end end end diff --git a/test/test_paulis.jl b/test/test_paulis.jl index d39ac2cfd..778282706 100644 --- a/test/test_paulis.jl +++ b/test/test_paulis.jl @@ -1,10 +1,7 @@ -using QuantumClifford +@testitem "Pauli Operators" begin + using QuantumClifford: apply_single_x!, apply_single_y!, apply_single_z! + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -using QuantumClifford: apply_single_x!, apply_single_y!, apply_single_z! - -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. - -@testset "Pauli Operators" begin @testset "Parsing, constructors, and properties" begin @test P"-iXYZ" == PauliOperator(0x3, 3, vcat(BitArray([1,1,0]).chunks, BitArray([0,1,1]).chunks)) @test P"-iXYZ" == PauliOperator(0x3, Bool[1,1,0], Bool[0,1,1]) diff --git a/test/test_precompile.jl b/test/test_precompile.jl index 92bd82a6a..e43e99a4b 100644 --- a/test/test_precompile.jl +++ b/test/test_precompile.jl @@ -1,3 +1,3 @@ -using QuantumClifford - -QuantumClifford._precompile_() +@testitem "Precompile" begin + QuantumClifford._precompile_() +end diff --git a/test/test_projections.jl b/test/test_projections.jl index e121d2abf..03d56e1e2 100644 --- a/test/test_projections.jl +++ b/test/test_projections.jl @@ -1,20 +1,19 @@ -using QuantumClifford +@testitem "Projective measurements" begin + using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good + using QuantumClifford: projectremoverand! -using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good -using QuantumClifford: projectremoverand! + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -@testset "Projective measurements" begin @testset "Stabilizer representation" begin s = S"XXX - ZZI - IZZ" + ZZI + IZZ" ps, anticom, res = project!(copy(s), P"ZII") ps = canonicalize!(ps) @test anticom==1 && isnothing(res) && ps == S"ZII - IZI - IIZ" + IZI + IIZ" @test stab_looks_good(ps) ps, anticom, res = project!(copy(s), P"-XXX") @@ -65,7 +64,7 @@ test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off end @testset "Anticommutation indices and NA results" begin s = S" XXX - -ZZI" + -ZZI" ds = Destabilizer(copy(s)) ms = MixedStabilizer(copy(s)) mds = MixedDestabilizer(copy(s)) @@ -125,21 +124,21 @@ test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off s, a, r = project!(copy(stab), projzl) @test mixed_destab_looks_good(s) @test a==0 && r==0x0 && stabilizerview(s)==S"Z___ - _Z__" + _Z__" s, a, r = project!(copy(stab), projxl) @test mixed_destab_looks_good(s) @test a==1 && isnothing(r) && stabilizerview(s)==S"X___ - _Z__" + _Z__" s, a, r = project!(copy(stab), projzr) @test mixed_destab_looks_good(s) @test a==3 && isnothing(r) && stabilizerview(s)==S"Z___ - _Z__ - ___Z" + _Z__ + ___Z" s, a, r = project!(copy(stab), projxr) @test mixed_destab_looks_good(s) @test a==3 && isnothing(r) && stabilizerview(s)==S"Z___ - _Z__ - ___X" + _Z__ + ___X" end @testset "Interface Particularities" begin s = S"ZII IZI" @@ -349,21 +348,21 @@ test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off @testset "Redundant row permutations in `project!(::MixedDestabilizer)`" begin # Fixed in 41ed1d3c destab = T"+ ZX_Y_YXZ - + XY_Y____ - + _Z_XXY__ - + _ZYXXY__ - + X__Y_ZXZ - + X__YXZXZ - + ___YXXZZ - + _______Z" + + XY_Y____ + + _Z_XXY__ + + _ZYXXY__ + + X__Y_ZXZ + + X__YXZXZ + + ___YXXZZ + + _______Z" stab = T"+ X_______ - + _X_Y____ - + __ZY____ - + __Z_____ - + ___YZY__ - + X__YZYZZ - + X____YZZ - + ______YX" + + _X_Y____ + + __ZY____ + + __Z_____ + + ___YZY__ + + X__YZYZZ + + X____YZZ + + ______YX" t = MixedDestabilizer(vcat(destab,stab), 8) @test mixed_destab_looks_good(t) c = copy(stabilizerview(t)[[1,3,5,7]]) diff --git a/test/test_quantumoptics.jl b/test/test_quantumoptics.jl index 9637daba4..5372a6e0c 100644 --- a/test/test_quantumoptics.jl +++ b/test/test_quantumoptics.jl @@ -1,109 +1,109 @@ -using Test -using QuantumClifford -using QuantumOpticsBase -using QuantumClifford: @S_str, random_stabilizer -using LinearAlgebra -#using QuantumCliffordQOpticsExt: _l0, _l1, _s₊, _s₋, _i₊, _i₋ -const qo = Base.get_extension(QuantumClifford, :QuantumCliffordQOpticsExt) -const _l0 = qo._l0 -const _l1 = qo._l1 -const _s₊ = qo._s₊ -const _s₋ = qo._s₋ -const _i₊ = qo._i₊ -const _i₋ = qo._i₋ +@testitem "Quantum optics" begin + using QuantumOpticsBase + using QuantumClifford: @S_str, random_stabilizer + using LinearAlgebra + #using QuantumCliffordQOpticsExt: _l0, _l1, _s₊, _s₋, _i₊, _i₋ + const qo = Base.get_extension(QuantumClifford, :QuantumCliffordQOpticsExt) + const _l0 = qo._l0 + const _l1 = qo._l1 + const _s₊ = qo._s₊ + const _s₋ = qo._s₋ + const _i₊ = qo._i₊ + const _i₋ = qo._i₋ -@testset "conversion from Stabilizer to Ket" begin - for n in 1:5 - stabs = [random_stabilizer(1) for _ in 1:n] - stab = tensor(stabs...) - translate = Dict(S"X"=>_s₊,S"-X"=>_s₋,S"Z"=>_l0,S"-Z"=>_l1,S"Y"=>_i₊,S"-Y"=>_i₋) - kets = [translate[s] for s in stabs] - ket = tensor(kets...) - @test ket.data ≈ Ket(stab).data + @testset "conversion from Stabilizer to Ket" begin + for n in 1:5 + stabs = [random_stabilizer(1) for _ in 1:n] + stab = tensor(stabs...) + translate = Dict(S"X"=>_s₊,S"-X"=>_s₋,S"Z"=>_l0,S"-Z"=>_l1,S"Y"=>_i₊,S"-Y"=>_i₋) + kets = [translate[s] for s in stabs] + ket = tensor(kets...) + @test ket.data ≈ Ket(stab).data - rstab = random_stabilizer(n) - lstab = random_stabilizer(n) - lket = Ket(rstab) - rket = Ket(lstab) - dotket = abs(lket'*rket) - dotstab = abs(dot(lstab,rstab)) - @test (dotket<=1e-10 && dotstab==0) || dotket≈dotstab + rstab = random_stabilizer(n) + lstab = random_stabilizer(n) + lket = Ket(rstab) + rket = Ket(lstab) + dotket = abs(lket'*rket) + dotstab = abs(dot(lstab,rstab)) + @test (dotket<=1e-10 && dotstab==0) || dotket≈dotstab + end end -end -@testset "conversion from CliffordOperator to Operator" begin - for n in 1:3 - for _c in 1:5 - cliff = random_clifford(n) - U = Operator(cliff) + @testset "conversion from CliffordOperator to Operator" begin + for n in 1:3 for _c in 1:5 - stab = random_stabilizer(n) - ψ₁ = Ket(stab) - ψ₂ = Ket(apply!(stab,cliff)) - # test they are equal up to a phase - @test all(x->isnan(x)||abs(x)≈1 , (U*ψ₁).data ./ ψ₂.data) - @test abs(det(U.data))≈1 + cliff = random_clifford(n) + U = Operator(cliff) + for _c in 1:5 + stab = random_stabilizer(n) + ψ₁ = Ket(stab) + ψ₂ = Ket(apply!(stab,cliff)) + # test they are equal up to a phase + @test all(x->isnan(x)||abs(x)≈1 , (U*ψ₁).data ./ ψ₂.data) + @test abs(det(U.data))≈1 + end end end end -end -@testset "conversion from StabMixture to Operator" begin - for n in 1:5 - stab = random_stabilizer(n) - @test dm(Ket(stab)) == Operator(StabMixture(stab)) + @testset "conversion from StabMixture to Operator" begin + for n in 1:5 + stab = random_stabilizer(n) + @test dm(Ket(stab)) == Operator(StabMixture(stab)) + end end -end -@testset "conversion from PauliOperator to Operator" begin - for n in 1:5 - for _ in 1:10 - p = random_pauli(n) - q = random_pauli(n) - p̃ = Operator(p) - q̃ = Operator(q) - @test Operator(p*q) == p̃*q̃ - @test Operator(q*p) == q̃*p̃ + @testset "conversion from PauliOperator to Operator" begin + for n in 1:5 + for _ in 1:10 + p = random_pauli(n) + q = random_pauli(n) + p̃ = Operator(p) + q̃ = Operator(q) + @test Operator(p*q) == p̃*q̃ + @test Operator(q*p) == q̃*p̃ + end end end -end -tgate = sparse(identityoperator(SpinBasis(1//2))) -tgate.data[2,2] = exp(im*pi/4) + tgate = sparse(identityoperator(SpinBasis(1//2))) + tgate.data[2,2] = exp(im*pi/4) -@testset "StabMixture/PauliChannel to QuantumOptics - explicit single-qubit Pauli channels" begin - # manual checks - @test Operator(pcT)≈tgate + @testset "StabMixture/PauliChannel to QuantumOptics - explicit single-qubit Pauli channels" begin + # manual checks + @test Operator(pcT)≈tgate - # single qubit checks - for single_qubit_explicit_channel in [pcT] - qo_gate = Operator(single_qubit_explicit_channel) - for single_qubit_tableau in [S"X", S"Y", S"Z", S"-X", S"-Y", S"-Z"] - sm = StabMixture(single_qubit_tableau) - ψ = Ket(single_qubit_tableau) - for rep in 1:8 - apply!(sm, single_qubit_explicit_channel) - ψ = qo_gate*ψ - @test expect(Operator(sm), ψ) ≈ 1 + # single qubit checks + for single_qubit_explicit_channel in [pcT] + qo_gate = Operator(single_qubit_explicit_channel) + for single_qubit_tableau in [S"X", S"Y", S"Z", S"-X", S"-Y", S"-Z"] + sm = StabMixture(single_qubit_tableau) + ψ = Ket(single_qubit_tableau) + for rep in 1:8 + apply!(sm, single_qubit_explicit_channel) + ψ = qo_gate*ψ + @test expect(Operator(sm), ψ) ≈ 1 + end end end - end - # embedded checks - for single_qubit_explicit_channel in [pcT] - for n in 2:5 - i = rand(1:n) - channel = embed(n,i,single_qubit_explicit_channel) - qo_gate1 = Operator(single_qubit_explicit_channel) - qo_gate = embed(basis(qo_gate1)^n, i, qo_gate1) - stab = random_stabilizer(n) - sm = StabMixture(stab) - ψ = Ket(stab) - for rep in 1:8 - apply!(sm, channel) - ψ = qo_gate*ψ - @test expect(Operator(sm), ψ) ≈ 1 + # embedded checks + for single_qubit_explicit_channel in [pcT] + for n in 2:5 + i = rand(1:n) + channel = embed(n,i,single_qubit_explicit_channel) + qo_gate1 = Operator(single_qubit_explicit_channel) + qo_gate = embed(basis(qo_gate1)^n, i, qo_gate1) + stab = random_stabilizer(n) + sm = StabMixture(stab) + ψ = Ket(stab) + for rep in 1:8 + apply!(sm, channel) + ψ = qo_gate*ψ + @test expect(Operator(sm), ψ) ≈ 1 + end end end end diff --git a/test/test_random.jl b/test/test_random.jl index ee97ffaf8..185de0d22 100644 --- a/test/test_random.jl +++ b/test/test_random.jl @@ -1,58 +1,58 @@ -using QuantumClifford -using Test +@testitem "Random" begin + using QuantumClifford + using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good -using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. - -@testset "Random sampling of operators" begin - for n in [1, test_sizes..., 200,500] - p = random_pauli(n) - s = random_stabilizer(n) - ss = random_stabilizer(rand(1:n),n) - ms = MixedDestabilizer(ss) - d = random_destabilizer(n) - c = random_clifford(n) - sq = random_clifford1(n÷2+1) - @test stab_looks_good(s) - @test stab_looks_good(ss) - @test destab_looks_good(d) - @test mixed_destab_looks_good(ms) - @test stab_looks_good(c*s) - @test stab_looks_good(c*ss) - @test destab_looks_good(c*d) - @test mixed_destab_looks_good(c*ms) - @test stab_looks_good(p*s) - @test stab_looks_good(p*ss) - @test destab_looks_good(p*d) - @test mixed_destab_looks_good(p*ms) - @test stab_looks_good(apply!(s,sq,phases=false)) - @test stab_looks_good(apply!(ss,sq,phases=false)) - @test destab_looks_good(apply!(d,sq,phases=false)) - @test mixed_destab_looks_good(apply!(ms,sq,phases=false)) + @testset "Random sampling of operators" begin + for n in [1, test_sizes..., 200,500] + p = random_pauli(n) + s = random_stabilizer(n) + ss = random_stabilizer(rand(1:n),n) + ms = MixedDestabilizer(ss) + d = random_destabilizer(n) + c = random_clifford(n) + sq = random_clifford1(n÷2+1) + @test stab_looks_good(s) + @test stab_looks_good(ss) + @test destab_looks_good(d) + @test mixed_destab_looks_good(ms) + @test stab_looks_good(c*s) + @test stab_looks_good(c*ss) + @test destab_looks_good(c*d) + @test mixed_destab_looks_good(c*ms) + @test stab_looks_good(p*s) + @test stab_looks_good(p*ss) + @test destab_looks_good(p*d) + @test mixed_destab_looks_good(p*ms) + @test stab_looks_good(apply!(s,sq,phases=false)) + @test stab_looks_good(apply!(ss,sq,phases=false)) + @test destab_looks_good(apply!(d,sq,phases=false)) + @test mixed_destab_looks_good(apply!(ms,sq,phases=false)) + end end -end -@testset "Random Paulis" begin - for n in [1, test_sizes..., 200,500] - @test all((random_pauli(n).phase[] == 0 for _ in 1:100)) - @test all((random_pauli(n, 0.1).phase[] == 0 for _ in 1:100)) - @test any((random_pauli(n; nophase=false, realphase=false).phase[] == 1 for _ in 1:100)) - @test any((random_pauli(n, 0.1; nophase=false, realphase=false).phase[] == 1 for _ in 1:100)) - @test any((random_pauli(n; nophase=false).phase[] ∈ [0,2] for _ in 1:100)) - @test any((random_pauli(n, 0.1; nophase=false).phase[] ∈ [0,2] for _ in 1:100)) - end - for i in 1:10 - e = 0.2 - n = 10000 - expected = 2/3*e * 2 * n - bound = 1/sqrt(n) - @test expected * (1-10bound) <= sum(count_ones.(random_pauli(10000,0.2).xz)) <= expected * (1+10bound) - e = 0.75 - n = 10000 - expected = 2/3*e * 2 * n - bound = 1/sqrt(n) - @test expected * (1-10bound) <= sum(count_ones.(random_pauli(10000).xz)) <= expected * (1+10bound) + @testset "Random Paulis" begin + for n in [1, test_sizes..., 200,500] + @test all((random_pauli(n).phase[] == 0 for _ in 1:100)) + @test all((random_pauli(n, 0.1).phase[] == 0 for _ in 1:100)) + @test any((random_pauli(n; nophase=false, realphase=false).phase[] == 1 for _ in 1:100)) + @test any((random_pauli(n, 0.1; nophase=false, realphase=false).phase[] == 1 for _ in 1:100)) + @test any((random_pauli(n; nophase=false).phase[] ∈ [0,2] for _ in 1:100)) + @test any((random_pauli(n, 0.1; nophase=false).phase[] ∈ [0,2] for _ in 1:100)) + end + for i in 1:10 + e = 0.2 + n = 10000 + expected = 2/3*e * 2 * n + bound = 1/sqrt(n) + @test expected * (1-10bound) <= sum(count_ones.(random_pauli(10000,0.2).xz)) <= expected * (1+10bound) + e = 0.75 + n = 10000 + expected = 2/3*e * 2 * n + bound = 1/sqrt(n) + @test expected * (1-10bound) <= sum(count_ones.(random_pauli(10000).xz)) <= expected * (1+10bound) + end end end diff --git a/test/test_stabcanon.jl b/test/test_stabcanon.jl index 5c113c69b..9db49ba8a 100644 --- a/test/test_stabcanon.jl +++ b/test/test_stabcanon.jl @@ -1,10 +1,6 @@ -using QuantumClifford - -using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good - -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. - -@testset "Stabilizer canonicalization" begin +@testitem "Stabilizer canonicalization" begin + using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. @testset "Default canonicalization" begin s = S"- XZZZZ_____ - _YZY___YX_ @@ -75,7 +71,7 @@ test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off end end -@testset "canonicalization invariants" begin +@testitem "canonicalization invariants" begin s = random_stabilizer(40,100) ss = tensor_pow(s,20) sa1 = canonicalize!(canonicalize_rref!(copy(ss))[1]) diff --git a/test/test_stabs.jl b/test/test_stabs.jl index 59a81d441..63a5c562e 100644 --- a/test/test_stabs.jl +++ b/test/test_stabs.jl @@ -1,93 +1,93 @@ -using Test -using QuantumClifford +@testitem "Stabilizers" begin + using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. + @testset "Pure and Mixed state initialization" begin -using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good - -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. - -@testset "Pure and Mixed state initialization" begin - @testset "Destabilizer initialization" begin - for n in test_sizes - s = random_stabilizer(n) - d = Destabilizer(s) - @test destab_looks_good(d) - canonicalize!(s) - s1 = copy(stabilizerview(d)) - canonicalize!(s1) - @test s1 == s + @testset "Destabilizer initialization" begin + for n in test_sizes + s = random_stabilizer(n) + d = Destabilizer(s) + @test destab_looks_good(d) + canonicalize!(s) + s1 = copy(stabilizerview(d)) + canonicalize!(s1) + @test s1 == s + end + end + @testset "Mixed destabilizer initialization" begin + for n in test_sizes + n<10 && continue + s = random_stabilizer(rand(n÷2+1:n-4),n) + md = MixedDestabilizer(s) + ms = MixedStabilizer(s) + @test mixed_stab_looks_good(ms) + @test mixed_destab_looks_good(md) + canonicalize!(s) + s1 = copy(stabilizerview(md)) + canonicalize!(s1) + @test s1 == s == stabilizerview(canonicalize!(ms)) + end + # Test initialization out of overdetermined stabs + stabs = [S"XX + II", + S"XX + XX", + S"ZZ + ZZ"] + for s in stabs + md = MixedDestabilizer(s[1:1]) + @test MixedDestabilizer(s) == md + end end end - @testset "Mixed destabilizer initialization" begin + + @testset "Tensor products over stabilizers" begin for n in test_sizes n<10 && continue - s = random_stabilizer(rand(n÷2+1:n-4),n) - md = MixedDestabilizer(s) - ms = MixedStabilizer(s) - @test mixed_stab_looks_good(ms) - @test mixed_destab_looks_good(md) + l = random_stabilizer(rand(n÷2+1:n-2),n) + r = random_stabilizer(rand(n÷3:n÷2),rand(n÷2:n)) + s = l⊗r + ds = MixedDestabilizer(l)⊗MixedDestabilizer(r) + @test mixed_destab_looks_good(ds) canonicalize!(s) - s1 = copy(stabilizerview(md)) - canonicalize!(s1) - @test s1 == s == stabilizerview(canonicalize!(ms)) - end - # Test initialization out of overdetermined stabs - stabs = [S"XX - II", - S"XX - XX", - S"ZZ - ZZ"] - for s in stabs - md = MixedDestabilizer(s[1:1]) - @test MixedDestabilizer(s) == md + dss = canonicalize!(copy(stabilizerview(ds))) + @test s == dss + stabs = [s[1:i] for s in [random_stabilizer(n) for n in [32,16,16,64,63,65,129,128,127]] for i in rand(1:10)]; + mdstabs = MixedDestabilizer.(stabs); + @test canonicalize!(⊗(stabs...)) == canonicalize!(stabilizerview(⊗(mdstabs...))) end end -end -@testset "Tensor products over stabilizers" begin - for n in test_sizes - n<10 && continue - l = random_stabilizer(rand(n÷2+1:n-2),n) - r = random_stabilizer(rand(n÷3:n÷2),rand(n÷2:n)) - s = l⊗r - ds = MixedDestabilizer(l)⊗MixedDestabilizer(r) - @test mixed_destab_looks_good(ds) - canonicalize!(s) - dss = canonicalize!(copy(stabilizerview(ds))) - @test s == dss - stabs = [s[1:i] for s in [random_stabilizer(n) for n in [32,16,16,64,63,65,129,128,127]] for i in rand(1:10)]; - mdstabs = MixedDestabilizer.(stabs); - @test canonicalize!(⊗(stabs...)) == canonicalize!(stabilizerview(⊗(mdstabs...))) - end -end -@testset "Stabilizer indexing" begin - s = random_stabilizer(9,10) - @test s[1,1] == s[[1,3,4],[1,3,5]][1,1] - @test s[1,1] == s[:,[1,3,5]][1,1] - @test s[1,1] == s[1,[1,3,5]][1] - @test s[1,1] == s[[1,3,5],:][1,1] - @test s[1,1] == s[[1,3,5],1][1,1] - @test s[1,1] == s[:,1][1,1] - @test s[1,1] == s[1,:][1] - @test s[1,1] == s[:,:][1,1] - @test isa(s[1], PauliOperator) - @test isa(s[1,:], PauliOperator) - @test isa(s[1,[1,2,3]], PauliOperator) - @test axes(s) == (axes(s,1), axes(s,2)) == (Base.OneTo(9),Base.OneTo(10)) - ms = MixedStabilizer(s) - mds = MixedStabilizer(s) - @test length(mds) == length(ms) == 10 - @test length(s) == 9 - for n in test_sizes - s = random_stabilizer(n) - r1 = rand(1:n) - ri1 = deleteat!(collect(1:n),r1) - s1a = QuantumClifford.remove_column!(copy(s),r1) - s1b = copy(s)[:,ri1] - r2 = min(n,rand([63,64,65])) - ri2 = deleteat!(collect(1:n),r2) - s2a = QuantumClifford.remove_column!(copy(s),r2) - s2b = copy(s)[:,ri2] - @test stab_to_gf2(s1a) == stab_to_gf2(s1b) - @test stab_to_gf2(s2a) == stab_to_gf2(s2b) + + @testset "Stabilizer indexing" begin + s = random_stabilizer(9,10) + @test s[1,1] == s[[1,3,4],[1,3,5]][1,1] + @test s[1,1] == s[:,[1,3,5]][1,1] + @test s[1,1] == s[1,[1,3,5]][1] + @test s[1,1] == s[[1,3,5],:][1,1] + @test s[1,1] == s[[1,3,5],1][1,1] + @test s[1,1] == s[:,1][1,1] + @test s[1,1] == s[1,:][1] + @test s[1,1] == s[:,:][1,1] + @test isa(s[1], PauliOperator) + @test isa(s[1,:], PauliOperator) + @test isa(s[1,[1,2,3]], PauliOperator) + @test axes(s) == (axes(s,1), axes(s,2)) == (Base.OneTo(9),Base.OneTo(10)) + ms = MixedStabilizer(s) + mds = MixedStabilizer(s) + @test length(mds) == length(ms) == 10 + @test length(s) == 9 + for n in test_sizes + s = random_stabilizer(n) + r1 = rand(1:n) + ri1 = deleteat!(collect(1:n),r1) + s1a = QuantumClifford.remove_column!(copy(s),r1) + s1b = copy(s)[:,ri1] + r2 = min(n,rand([63,64,65])) + ri2 = deleteat!(collect(1:n),r2) + s2a = QuantumClifford.remove_column!(copy(s),r2) + s2b = copy(s)[:,ri2] + @test stab_to_gf2(s1a) == stab_to_gf2(s1b) + @test stab_to_gf2(s2a) == stab_to_gf2(s2b) + end end end diff --git a/test/test_sumtypecompactification.jl b/test/test_sumtypecompactification.jl index 11c902d4e..63974bed8 100644 --- a/test/test_sumtypecompactification.jl +++ b/test/test_sumtypecompactification.jl @@ -1,7 +1,4 @@ -using Test -using QuantumClifford - -@testset "SumTypes compactification" begin +@testitem "SumTypes compactification" begin @test_warn "Could not compactify the circuit" QuantumClifford.pftrajectories([ClassicalXOR{17}((65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81), 282)]) QuantumClifford.compactify_circuit([ClassicalXOR{3}((65, 66, 67), 282)]) end diff --git a/test/test_symcliff.jl b/test/test_symcliff.jl index 2af1fd538..66ea60154 100644 --- a/test/test_symcliff.jl +++ b/test/test_symcliff.jl @@ -1,80 +1,80 @@ -using Random -using QuantumClifford -using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good -using QuantumClifford: apply_single_x!, apply_single_y!, apply_single_z! -using InteractiveUtils -using Test -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. +@testitem "Symbolic Clifford" begin + using Random + using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good + using QuantumClifford: apply_single_x!, apply_single_y!, apply_single_z! + using InteractiveUtils + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. -@testset "Small symbolic operators" begin - for n in test_sizes - for i in 1:6 - op = enumerate_single_qubit_gates(i, qubit=n, phases=(rand(Bool),rand(Bool))) - op0 = enumerate_single_qubit_gates(i, qubit=n) - op_cc = CliffordOperator(op, 1, compact=true) - op_c = CliffordOperator(op, n) - @test SingleQubitOperator(op)==SingleQubitOperator(op_cc, n) - op0_c = CliffordOperator(op0, n) + @testset "Small symbolic operators" begin + for n in test_sizes + for i in 1:6 + op = enumerate_single_qubit_gates(i, qubit=n, phases=(rand(Bool),rand(Bool))) + op0 = enumerate_single_qubit_gates(i, qubit=n) + op_cc = CliffordOperator(op, 1, compact=true) + op_c = CliffordOperator(op, n) + @test SingleQubitOperator(op)==SingleQubitOperator(op_cc, n) + op0_c = CliffordOperator(op0, n) + s = random_stabilizer(n) + @test apply!(copy(s),op)==apply!(copy(s),SingleQubitOperator(op))==apply!(copy(s),op_cc,[n])==apply!(copy(s),op_c) + @test ==(apply!(copy(s),op,phases=false),apply!(copy(s),op_cc,[n],phases=false), phases=false) + @test apply!(copy(s),op0)==apply!(copy(s),op0_c) + end + i = n÷2+1 + @test apply!(copy(s),sX(i)) == apply_single_x!(copy(s),i) + @test apply!(copy(s),sY(i)) == apply_single_y!(copy(s),i) + @test apply!(copy(s),sZ(i)) == apply_single_z!(copy(s),i) + n==1 && continue s = random_stabilizer(n) - @test apply!(copy(s),op)==apply!(copy(s),SingleQubitOperator(op))==apply!(copy(s),op_cc,[n])==apply!(copy(s),op_c) - @test ==(apply!(copy(s),op,phases=false),apply!(copy(s),op_cc,[n],phases=false), phases=false) - @test apply!(copy(s),op0)==apply!(copy(s),op0_c) + i1,i2 = randperm(n)[1:2] + @test apply!(copy(s),tCNOT,[i1,i2]) == apply!(copy(s),sCNOT(i1,i2)) + @test apply!(copy(s),tSWAP,[i1,i2]) == apply!(copy(s),sSWAP(i1,i2)) + @test apply!(copy(s),tCPHASE,[i1,i2]) == apply!(copy(s),sCPHASE(i1,i2)) end - i = n÷2+1 - @test apply!(copy(s),sX(i)) == apply_single_x!(copy(s),i) - @test apply!(copy(s),sY(i)) == apply_single_y!(copy(s),i) - @test apply!(copy(s),sZ(i)) == apply_single_z!(copy(s),i) - n==1 && continue - s = random_stabilizer(n) - i1,i2 = randperm(n)[1:2] - @test apply!(copy(s),tCNOT,[i1,i2]) == apply!(copy(s),sCNOT(i1,i2)) - @test apply!(copy(s),tSWAP,[i1,i2]) == apply!(copy(s),sSWAP(i1,i2)) - @test apply!(copy(s),tCPHASE,[i1,i2]) == apply!(copy(s),sCPHASE(i1,i2)) + @test_throws DimensionMismatch SingleQubitOperator(tCNOT,1) + @test_throws DimensionMismatch CliffordOperator(sHadamard(5),2) + @test_throws ArgumentError CliffordOperator(sHadamard(5),6,compact=true) end - @test_throws DimensionMismatch SingleQubitOperator(tCNOT,1) - @test_throws DimensionMismatch CliffordOperator(sHadamard(5),2) - @test_throws ArgumentError CliffordOperator(sHadamard(5),6,compact=true) -end -@testset "Convert between small ops" begin - for op in subtypes(QuantumClifford.AbstractSingleQubitOperator) - op == SingleQubitOperator && continue - sop = op(1) - sqsop = SingleQubitOperator(sop) - cop = CliffordOperator(sop,1) - csqop = CliffordOperator(sqsop,1) - tcop = CliffordOperator(op) - stcop = SingleQubitOperator(tcop) - s = random_destabilizer(1) - @test sop*s == sqsop*s == cop*s == csqop*s == tcop*s == stcop*s - end - for op in subtypes(QuantumClifford.AbstractTwoQubitOperator) - sop = op(1,2) - cop = CliffordOperator(sop,2) - ccop = CliffordOperator(sop,2; compact=true) - @test_throws DimensionMismatch CliffordOperator(op(1,4),3) - @test_throws ArgumentError CliffordOperator(sop,3; compact=true) - tcop = CliffordOperator(op) - s = random_destabilizer(2) - @test sop*s == cop*s == tcop*s == ccop*s - end - for op in subtypes(QuantumClifford.AbstractTwoQubitOperator) - sop = op(1,10) - op1 = CliffordOperator(sop,20) - op2 = sSWAP(2,10)*(CliffordOperator(sop,2; compact=true)⊗tensor_pow(tId1, 18))*CliffordOperator(sSWAP(2,10),20) - @test op1 == op2 + @testset "Convert between small ops" begin + for op in subtypes(QuantumClifford.AbstractSingleQubitOperator) + op == SingleQubitOperator && continue + sop = op(1) + sqsop = SingleQubitOperator(sop) + cop = CliffordOperator(sop,1) + csqop = CliffordOperator(sqsop,1) + tcop = CliffordOperator(op) + stcop = SingleQubitOperator(tcop) + s = random_destabilizer(1) + @test sop*s == sqsop*s == cop*s == csqop*s == tcop*s == stcop*s + end + for op in subtypes(QuantumClifford.AbstractTwoQubitOperator) + sop = op(1,2) + cop = CliffordOperator(sop,2) + ccop = CliffordOperator(sop,2; compact=true) + @test_throws DimensionMismatch CliffordOperator(op(1,4),3) + @test_throws ArgumentError CliffordOperator(sop,3; compact=true) + tcop = CliffordOperator(op) + s = random_destabilizer(2) + @test sop*s == cop*s == tcop*s == ccop*s + end + for op in subtypes(QuantumClifford.AbstractTwoQubitOperator) + sop = op(1,10) + op1 = CliffordOperator(sop,20) + op2 = sSWAP(2,10)*(CliffordOperator(sop,2; compact=true)⊗tensor_pow(tId1, 18))*CliffordOperator(sSWAP(2,10),20) + @test op1 == op2 + end end -end -@testset "SingleQubitOperator inv methods" begin - for gate_type in [sHadamard, sX, sY, sZ, sId1 , sPhase, sInvPhase] - n = rand(1:10) - @test CliffordOperator(inv(SingleQubitOperator(gate_type(n))), n) == inv(CliffordOperator(gate_type(n), n)) - @test CliffordOperator(inv(gate_type(n)), n) == inv(CliffordOperator(gate_type(n), n)) - end - for i in 1:10 - random_op = random_clifford1(i) - @test CliffordOperator(inv(random_op), i) == inv(CliffordOperator(random_op, i)) - @test CliffordOperator(inv(SingleQubitOperator(random_op)), i) == inv(CliffordOperator(random_op, i)) + @testset "SingleQubitOperator inv methods" begin + for gate_type in [sHadamard, sX, sY, sZ, sId1 , sPhase, sInvPhase] + n = rand(1:10) + @test CliffordOperator(inv(SingleQubitOperator(gate_type(n))), n) == inv(CliffordOperator(gate_type(n), n)) + @test CliffordOperator(inv(gate_type(n)), n) == inv(CliffordOperator(gate_type(n), n)) + end + for i in 1:10 + random_op = random_clifford1(i) + @test CliffordOperator(inv(random_op), i) == inv(CliffordOperator(random_op, i)) + @test CliffordOperator(inv(SingleQubitOperator(random_op)), i) == inv(CliffordOperator(random_op, i)) + end end end diff --git a/test/test_symcontrolled.jl b/test/test_symcontrolled.jl index e69553ed4..256f62be3 100644 --- a/test/test_symcontrolled.jl +++ b/test/test_symcontrolled.jl @@ -1,128 +1,128 @@ -using Test -using QuantumClifford -using QuantumOpticsBase +@testitem "Controlled" begin + using QuantumOpticsBase -function transform_Zbasis(qubit) - transformations = Dict(:X => [sHadamard(qubit),], :Y => [sInvPhase(qubit),sHadamard(qubit)], :Z => [sHadamard(qubit), sHadamard(qubit)]) - inverse_transformations = Dict(:X => [sHadamard(qubit),], :Y => [sHadamard(qubit), sPhase(qubit)], :Z => [sHadamard(qubit), sHadamard(qubit)]) - return transformations, inverse_transformations -end + function transform_Zbasis(qubit) + transformations = Dict(:X => [sHadamard(qubit),], :Y => [sInvPhase(qubit),sHadamard(qubit)], :Z => [sHadamard(qubit), sHadamard(qubit)]) + inverse_transformations = Dict(:X => [sHadamard(qubit),], :Y => [sHadamard(qubit), sPhase(qubit)], :Z => [sHadamard(qubit), sHadamard(qubit)]) + return transformations, inverse_transformations + end -function transform_Xbasis(qubit) - transformations = Dict(:Z => [sHadamard(qubit),], :Y => [sInvPhase(qubit),], :X => [sHadamard(qubit), sHadamard(qubit)]) - inverse_transformations = Dict(:Z => [sHadamard(qubit)], :Y => [sPhase(qubit),], :X => [sHadamard(qubit), sHadamard(qubit)]) - return transformations, inverse_transformations -end + function transform_Xbasis(qubit) + transformations = Dict(:Z => [sHadamard(qubit),], :Y => [sInvPhase(qubit),], :X => [sHadamard(qubit), sHadamard(qubit)]) + inverse_transformations = Dict(:Z => [sHadamard(qubit)], :Y => [sPhase(qubit),], :X => [sHadamard(qubit), sHadamard(qubit)]) + return transformations, inverse_transformations + end -function get_gates(control, target) - transform1, inverse1 = transform_Zbasis(1) - transform2, inverse2 = transform_Xbasis(2) - return [transform1[control]..., transform2[target]...], [inverse2[target]..., inverse1[control]...] -end + function get_gates(control, target) + transform1, inverse1 = transform_Zbasis(1) + transform2, inverse2 = transform_Xbasis(2) + return [transform1[control]..., transform2[target]...], [inverse2[target]..., inverse1[control]...] + end -@testset "Controlled gates" begin - for i in 1:10 - for control in (:X, :Y, :Z) - for target in (:X, :Y, :Z) - random_state = random_destabilizer(2) - implemented_gate = eval(Symbol(:s,control,:C,target))(1,2) + @testset "Controlled gates" begin + for i in 1:10 + for control in (:X, :Y, :Z) + for target in (:X, :Y, :Z) + random_state = random_destabilizer(2) + implemented_gate = eval(Symbol(:s,control,:C,target))(1,2) - tr, inv = get_gates(control, target) - test_gates = [tr..., sCNOT(1,2), inv...] - @test apply!(copy(random_state), implemented_gate) == mctrajectory!(copy(random_state), test_gates)[1] + tr, inv = get_gates(control, target) + test_gates = [tr..., sCNOT(1,2), inv...] + @test apply!(copy(random_state), implemented_gate) == mctrajectory!(copy(random_state), test_gates)[1] + end end end end -end -transforms = Dict(:X => transform_Xbasis(1), :Z => transform_Zbasis(1)) + transforms = Dict(:X => transform_Xbasis(1), :Z => transform_Zbasis(1)) -@testset "Change of basis" begin - for to_basis in (:X, :Y, :Z) - for from_basis in (:X, :Z) - start_state = Stabilizer(QuantumClifford._T_str(string(from_basis))) - forward, backward = transforms[from_basis][1][to_basis], transforms[from_basis][2][to_basis] - mid_state = mctrajectory!(copy(start_state), backward)[1] - end_state = mctrajectory!(copy(mid_state), forward)[1] - @test start_state == end_state - @test mid_state == Stabilizer(QuantumClifford._T_str(string(to_basis))) + @testset "Change of basis" begin + for to_basis in (:X, :Y, :Z) + for from_basis in (:X, :Z) + start_state = Stabilizer(QuantumClifford._T_str(string(from_basis))) + forward, backward = transforms[from_basis][1][to_basis], transforms[from_basis][2][to_basis] + mid_state = mctrajectory!(copy(start_state), backward)[1] + end_state = mctrajectory!(copy(mid_state), forward)[1] + @test start_state == end_state + @test mid_state == Stabilizer(QuantumClifford._T_str(string(to_basis))) + end end end -end -@testset "Explicit control" begin - for control in (:X, :Z, :Y) - for target in (:X, :Z, :Y) - for targetstate in ("X","Y","Z") - twoqgate = eval(Symbol(:s,control,:C,target))(1,2) - oneqgate = eval(Symbol(:s,target))(2) - state0str = string(control)*"I I"*targetstate - state1str = "-"*string(control)*"I I"*targetstate - state0 = Stabilizer(QuantumClifford._T_str(string(state0str))) - state1 = Stabilizer(QuantumClifford._T_str(string(state1str))) - @test canonicalize!(twoqgate*state0) == canonicalize!(state0) - @test canonicalize!(twoqgate*state1) == canonicalize!(oneqgate*state1) + @testset "Explicit control" begin + for control in (:X, :Z, :Y) + for target in (:X, :Z, :Y) + for targetstate in ("X","Y","Z") + twoqgate = eval(Symbol(:s,control,:C,target))(1,2) + oneqgate = eval(Symbol(:s,target))(2) + state0str = string(control)*"I I"*targetstate + state1str = "-"*string(control)*"I I"*targetstate + state0 = Stabilizer(QuantumClifford._T_str(string(state0str))) + state1 = Stabilizer(QuantumClifford._T_str(string(state1str))) + @test canonicalize!(twoqgate*state0) == canonicalize!(state0) + @test canonicalize!(twoqgate*state1) == canonicalize!(oneqgate*state1) + end end end end -end -@testset "Control-Target swap" begin - for control in (:X, :Z, :Y) - for target in (:X, :Z, :Y) - forwgate = eval(Symbol(:s,control,:C,target))(1,2) - backgate = eval(Symbol(:s,target,:C,control))(1,2) - forwgatedense = CliffordOperator(forwgate, 2) - backgatedense = CliffordOperator(backgate, 2) - @test forwgatedense == tSWAP*backgatedense*tSWAP + @testset "Control-Target swap" begin + for control in (:X, :Z, :Y) + for target in (:X, :Z, :Y) + forwgate = eval(Symbol(:s,control,:C,target))(1,2) + backgate = eval(Symbol(:s,target,:C,control))(1,2) + forwgatedense = CliffordOperator(forwgate, 2) + backgatedense = CliffordOperator(backgate, 2) + @test forwgatedense == tSWAP*backgatedense*tSWAP + end end - end - for control in (:X, :Z, :Y) - for target in (:X, :Z, :Y) - forwgate = eval(Symbol(:s,control,:C,target))(1,2) - backgate = eval(Symbol(:s,target,:C,control))(2,1) - forwgatedense = CliffordOperator(forwgate, 2) - backgatedense = CliffordOperator(backgate, 2) - @test forwgatedense == backgatedense + for control in (:X, :Z, :Y) + for target in (:X, :Z, :Y) + forwgate = eval(Symbol(:s,control,:C,target))(1,2) + backgate = eval(Symbol(:s,target,:C,control))(2,1) + forwgatedense = CliffordOperator(forwgate, 2) + backgatedense = CliffordOperator(backgate, 2) + @test forwgatedense == backgatedense + end end - end - for (gate1,gate2) in ( - (sCNOT(1,2), sZCX(1,2)), - (sCPHASE(1,2), sZCZ(1,2)), - ) - gate1dense = CliffordOperator(gate1, 2) - gate2dense = CliffordOperator(gate2, 2) - @test gate1dense == gate2dense + for (gate1,gate2) in ( + (sCNOT(1,2), sZCX(1,2)), + (sCPHASE(1,2), sZCZ(1,2)), + ) + gate1dense = CliffordOperator(gate1, 2) + gate2dense = CliffordOperator(gate2, 2) + @test gate1dense == gate2dense + end end -end -@testset "Ket-based definition" begin - for control in (:X, :Y, :Z) - for target in (:X, :Y, :Z) - s = Stabilizer(QuantumClifford._T_str(string(control))) - k1 = Ket(s) - s.tab.phases[1] = 0x2 - k2 = Ket(s) - i = Operator(tId1) - o = Operator(CliffordOperator(eval(Symbol(:s,target,))(1),1)) - gate = projector(k1)⊗i + (target==:Y ? -im : 1) * projector(k2)⊗o - implemented_gate = Operator(CliffordOperator(eval(Symbol(:s,control,:C,target))(1,2),2)) - @test gate≈implemented_gate + @testset "Ket-based definition" begin + for control in (:X, :Y, :Z) + for target in (:X, :Y, :Z) + s = Stabilizer(QuantumClifford._T_str(string(control))) + k1 = Ket(s) + s.tab.phases[1] = 0x2 + k2 = Ket(s) + i = Operator(tId1) + o = Operator(CliffordOperator(eval(Symbol(:s,target,))(1),1)) + gate = projector(k1)⊗i + (target==:Y ? -im : 1) * projector(k2)⊗o + implemented_gate = Operator(CliffordOperator(eval(Symbol(:s,control,:C,target))(1,2),2)) + @test gate≈implemented_gate - target, control = control, target - s = Stabilizer(QuantumClifford._T_str(string(control))) - k1 = Ket(s) - s.tab.phases[1] = 0x2 - k2 = Ket(s) - i = Operator(tId1) - o = Operator(CliffordOperator(eval(Symbol(:s,target,))(1),1)) - gate_perm = projector(k1)⊗i + (target==:Y ? -im : 1) * projector(k2)⊗o - implemented_gate_perm = Operator(CliffordOperator(eval(Symbol(:s,control,:C,target))(1,2),2)) - @test gate_perm≈implemented_gate_perm + target, control = control, target + s = Stabilizer(QuantumClifford._T_str(string(control))) + k1 = Ket(s) + s.tab.phases[1] = 0x2 + k2 = Ket(s) + i = Operator(tId1) + o = Operator(CliffordOperator(eval(Symbol(:s,target,))(1),1)) + gate_perm = projector(k1)⊗i + (target==:Y ? -im : 1) * projector(k2)⊗o + implemented_gate_perm = Operator(CliffordOperator(eval(Symbol(:s,control,:C,target))(1,2),2)) + @test gate_perm≈implemented_gate_perm - @test permutesystems(gate_perm,[2,1])≈gate + @test permutesystems(gate_perm,[2,1])≈gate + end end end end diff --git a/test/test_syndromemeas.jl b/test/test_syndromemeas.jl index 23526250d..3e6ef06b9 100644 --- a/test/test_syndromemeas.jl +++ b/test/test_syndromemeas.jl @@ -1,7 +1,5 @@ -using QuantumClifford -using QuantumClifford: AbstractOperation - -@testset "Syndrome Measurements with mctrajectory!" begin # TODO this is a rather old test that is now done in a few other places, e.g. the ECC module -- double check and consider deleting +@testitem "Syndrome Measurements with mctrajectory!" begin # TODO this is a rather old test that is now done in a few other places, e.g. the ECC module -- double check and consider deleting + using QuantumClifford: AbstractOperation codeˢᵗᵉᵃⁿᵉ = S"Z__Z_ZZ _Z_ZZ_Z __Z_ZZZ diff --git a/test/test_throws.jl b/test/test_throws.jl index 724cc7de5..2c67f4f4a 100644 --- a/test/test_throws.jl +++ b/test/test_throws.jl @@ -1,67 +1,66 @@ -using Test -using QuantumClifford -using QuantumClifford: rank, mul_left!, mul_right! -using InteractiveUtils: subtypes +@testitem "throws" begin + using QuantumClifford: rank, mul_left!, mul_right! + using InteractiveUtils: subtypes + @test_throws DimensionMismatch CliffordOperator(T"XXX ZZ_") -@test_throws DimensionMismatch CliffordOperator(T"XXX ZZ_") + @test_throws DimensionMismatch tCNOT*S"X" -@test_throws DimensionMismatch tCNOT*S"X" + #@test_throws DomainError bigram(random_stabilizer(50), clip=false) -#@test_throws DomainError bigram(random_stabilizer(50), clip=false) + @test_throws DomainError logdot(S"XX", S"XX ZZ") + @test_throws DimensionMismatch logdot(S"X", S"XX ZZ") -@test_throws DomainError logdot(S"XX", S"XX ZZ") -@test_throws DimensionMismatch logdot(S"X", S"XX ZZ") + @test_throws BadDataStructure rank(S"X") + @test_throws BadDataStructure rank(Destabilizer(S"X")) -@test_throws BadDataStructure rank(S"X") -@test_throws BadDataStructure rank(Destabilizer(S"X")) + @test_throws DimensionMismatch mul_left!(P"X", P"XX") + @test_throws DimensionMismatch mul_right!(P"X", P"XX") -@test_throws DimensionMismatch mul_left!(P"X", P"XX") -@test_throws DimensionMismatch mul_right!(P"X", P"XX") + @test_throws ArgumentError StabMixture(S"XX") -@test_throws ArgumentError StabMixture(S"XX") + @test_throws ArgumentError UnitaryPauliChannel([P"X"], [1,2]) + @test_throws ArgumentError UnitaryPauliChannel([P"X",P"XX"], [1,2]) -@test_throws ArgumentError UnitaryPauliChannel([P"X"], [1,2]) -@test_throws ArgumentError UnitaryPauliChannel([P"X",P"XX"], [1,2]) + @test_throws ArgumentError embed(10,2,P"XX") + @test_throws ArgumentError embed(10,[2,3],P"X") -@test_throws ArgumentError embed(10,2,P"XX") -@test_throws ArgumentError embed(10,[2,3],P"X") + struct A <: QuantumClifford.AbstractOperation end + @test_throws ArgumentError applybranches(S"X",A()) -struct A <: QuantumClifford.AbstractOperation end -@test_throws ArgumentError applybranches(S"X",A()) + @test_throws BadDataStructure project!(Destabilizer(S"XX"), P"ZZ") -@test_throws BadDataStructure project!(Destabilizer(S"XX"), P"ZZ") + @test_throws DimensionMismatch reset_qubits!(ghz(4), ghz(3), [1,2]) + @test_throws DimensionMismatch reset_qubits!(ghz(3), ghz(4), [1,2,3,4]) + @test_throws DimensionMismatch reset_qubits!(MixedStabilizer(ghz(4)), MixedStabilizer(ghz(3)), [1,2]) + @test_throws DimensionMismatch reset_qubits!(MixedStabilizer(ghz(3)), MixedStabilizer(ghz(4)), [1,2,3,4]) + @test_throws DimensionMismatch reset_qubits!(MixedDestabilizer(ghz(4)), MixedDestabilizer(ghz(3)), [1,2]) + @test_throws DimensionMismatch reset_qubits!(MixedDestabilizer(ghz(3)), MixedDestabilizer(ghz(4)), [1,2,3,4]) -@test_throws DimensionMismatch reset_qubits!(ghz(4), ghz(3), [1,2]) -@test_throws DimensionMismatch reset_qubits!(ghz(3), ghz(4), [1,2,3,4]) -@test_throws DimensionMismatch reset_qubits!(MixedStabilizer(ghz(4)), MixedStabilizer(ghz(3)), [1,2]) -@test_throws DimensionMismatch reset_qubits!(MixedStabilizer(ghz(3)), MixedStabilizer(ghz(4)), [1,2,3,4]) -@test_throws DimensionMismatch reset_qubits!(MixedDestabilizer(ghz(4)), MixedDestabilizer(ghz(3)), [1,2]) -@test_throws DimensionMismatch reset_qubits!(MixedDestabilizer(ghz(3)), MixedDestabilizer(ghz(4)), [1,2,3,4]) + #TODO broken in other ways @test_throws DomainError MixedDestabilizer(Destabilizer(S"XX")) -#TODO broken in other ways @test_throws DomainError MixedDestabilizer(Destabilizer(S"XX")) + @test_throws DomainError 2*P"X" -@test_throws DomainError 2*P"X" + @test_throws DimensionMismatch P"X" * S"XX" -@test_throws DimensionMismatch P"X" * S"XX" + @test_throws ArgumentError one(typeof(T"X"), 1, basis=:U) -@test_throws ArgumentError one(typeof(T"X"), 1, basis=:U) + for gt in subtypes(QuantumClifford.AbstractSingleQubitOperator) + gt == SingleQubitOperator && continue + @test_throws ArgumentError gt(0) + @test_throws ArgumentError gt(-1) + end -for gt in subtypes(QuantumClifford.AbstractSingleQubitOperator) - gt == SingleQubitOperator && continue - @test_throws ArgumentError gt(0) - @test_throws ArgumentError gt(-1) -end - -for gt in subtypes(QuantumClifford.AbstractTwoQubitOperator) - @test_throws ArgumentError gt(0,1) - @test_throws ArgumentError gt(-1,1) - @test_throws ArgumentError gt(2,2) -end + for gt in subtypes(QuantumClifford.AbstractTwoQubitOperator) + @test_throws ArgumentError gt(0,1) + @test_throws ArgumentError gt(-1,1) + @test_throws ArgumentError gt(2,2) + end -for m in [sMX,sMZ,sMY,sMRX,sMRZ,sMRY] - @test_throws ArgumentError m(0) - @test_throws ArgumentError m(-1) - @test_throws ArgumentError m(0,1) - @test_throws ArgumentError m(-1,0) + for m in [sMX,sMZ,sMY,sMRX,sMRZ,sMRY] + @test_throws ArgumentError m(0) + @test_throws ArgumentError m(-1) + @test_throws ArgumentError m(0,1) + @test_throws ArgumentError m(-1,0) + end end diff --git a/test/test_trace.jl b/test/test_trace.jl index bb74dd77f..424399ce1 100644 --- a/test/test_trace.jl +++ b/test/test_trace.jl @@ -1,109 +1,108 @@ -using Random -using QuantumClifford +@testitem "Trace" begin + using Random + using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good -using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good - -test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. - -@testset "Partial traces" begin - @testset "RREF canonicalization vs manual traceout" begin - for N in test_sizes - for n in [N,rand(N÷4:N÷2)] - to_delete = randperm(N)[1:rand(N÷4:N÷2)] - stab0 = random_stabilizer(n, N) - id_paulis = zero(PauliOperator, N) - # Trace out by doing projective measurements - naive_stab = copy(stab0) - for i in to_delete - naive_stab, anticom_index, result = project!(naive_stab, single_x(N,i)) - if anticom_index!=0 && anticom_index<=length(naive_stab) - naive_stab[anticom_index] = id_paulis + test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. + @testset "Partial traces" begin + @testset "RREF canonicalization vs manual traceout" begin + for N in test_sizes + for n in [N,rand(N÷4:N÷2)] + to_delete = randperm(N)[1:rand(N÷4:N÷2)] + stab0 = random_stabilizer(n, N) + id_paulis = zero(PauliOperator, N) + # Trace out by doing projective measurements + naive_stab = copy(stab0) + for i in to_delete + naive_stab, anticom_index, result = project!(naive_stab, single_x(N,i)) + if anticom_index!=0 && anticom_index<=length(naive_stab) + naive_stab[anticom_index] = id_paulis + end + naive_stab, anticom_index, result = project!(naive_stab, single_z(N,i)) + if anticom_index!=0 && anticom_index<=length(naive_stab) + naive_stab[anticom_index] = id_paulis + end end - naive_stab, anticom_index, result = project!(naive_stab, single_z(N,i)) - if anticom_index!=0 && anticom_index<=length(naive_stab) - naive_stab[anticom_index] = id_paulis + canonicalize!(naive_stab) + # Trace out by using the RREF canonical form + stab = copy(stab0) + stab, last_row = canonicalize_rref!(stab, to_delete) + for i in last_row+1:n + stab[i] = id_paulis end + canonicalize!(stab) + # Confirm the results are the same + @test stab == naive_stab + @test mixed_stab_looks_good(stab[1:last_row]) + # Check the built-in traceout! functions for this + s = traceout!(copy(stab0), to_delete) + canonicalize!(s) + @test stab == s + # On MixedStabilizer instances + s = traceout!(MixedStabilizer(copy(stab0), n), to_delete) + canonicalize!(s) + @test stab[1:last_row] == stabilizerview(s) + @test mixed_stab_looks_good(s) + # On MixedDestabilizer instances + s = traceout!(MixedDestabilizer(copy(stab0)), to_delete) + @test mixed_destab_looks_good(s) + s = canonicalize!(stabilizerview(s)) + @test stab[1:last_row] == s end - canonicalize!(naive_stab) - # Trace out by using the RREF canonical form - stab = copy(stab0) - stab, last_row = canonicalize_rref!(stab, to_delete) - for i in last_row+1:n - stab[i] = id_paulis - end - canonicalize!(stab) - # Confirm the results are the same - @test stab == naive_stab - @test mixed_stab_looks_good(stab[1:last_row]) - # Check the built-in traceout! functions for this - s = traceout!(copy(stab0), to_delete) - canonicalize!(s) - @test stab == s - # On MixedStabilizer instances - s = traceout!(MixedStabilizer(copy(stab0), n), to_delete) - canonicalize!(s) - @test stab[1:last_row] == stabilizerview(s) - @test mixed_stab_looks_good(s) - # On MixedDestabilizer instances - s = traceout!(MixedDestabilizer(copy(stab0)), to_delete) - @test mixed_destab_looks_good(s) - s = canonicalize!(stabilizerview(s)) - @test stab[1:last_row] == s end end end -end -@testset "Qubit resets" begin - @test_throws DimensionMismatch reset_qubits!(S"XX YY",S"X",[1,2]) - @test_throws DimensionMismatch reset_qubits!(S"X",S"XX YY",[1,2]) - @test_throws DimensionMismatch reset_qubits!(MixedStabilizer(S"XX YY"),S"X",[1,2]) - @test_throws DimensionMismatch reset_qubits!(MixedStabilizer(S"X"),S"XX YY",[1,2]) - @test_throws DimensionMismatch reset_qubits!(MixedDestabilizer(S"XX YY"),S"X",[1,2]) - @test_throws DimensionMismatch reset_qubits!(MixedDestabilizer(S"X"),S"XX YY",[1,2]) - for N in test_sizes - for R in [rand(N÷2:N*2÷3), N] - R > 0 || continue - s = random_stabilizer(R,N) - nnew = rand(N÷4:N*2÷3) - nnew > 0 || continue - newstate = random_stabilizer(nnew) - perm = randperm(N)[1:nqubits(newstate)] - to_trace = setdiff(1:N,perm) - resetop = Reset(newstate, perm) - # Testing MixedDestabilizer - md = MixedDestabilizer(s) - mdr1 = reset_qubits!(copy(md), newstate,perm) - @test mixed_destab_looks_good(mdr1) - mdr2 = reset_qubits!(copy(mdr1),newstate,perm) - mdro = apply!(copy(md), resetop) - @test canonicalize!(copy(stabilizerview(mdr1)))==canonicalize!(copy(stabilizerview(mdr2)))==canonicalize!(copy(stabilizerview(mdro))) - traceout!(mdr2,to_trace) - mdr2v = stabilizerview(mdr2) - @test canonicalize!(copy(mdr2v)[:,perm]) == canonicalize!(copy(newstate)) - # Testing MixedStabilizer - ms = MixedStabilizer(s) - msr1 = reset_qubits!(copy(ms), newstate,perm) - @test mixed_stab_looks_good(msr1) - msr2 = reset_qubits!(copy(msr1),newstate,perm) - msro = apply!(copy(ms), resetop) - @test msr1==msr2==msro - traceout!(msr2,to_trace) - msr2v = stabilizerview(msr2) - @test canonicalize!(copy(msr2v)[:,perm]) == canonicalize!(copy(newstate)) - @test canonicalize!(msr2v) == canonicalize!(mdr2v) - # Testing Stabilizer - ss = R==N ? s : Stabilizer(tab(MixedStabilizer(s))) # Ensure the tableau is padded with Is - ssr1 = reset_qubits!(copy(ss), newstate,perm) - ssr2 = reset_qubits!(copy(ssr1),newstate,perm) - ssro = apply!(copy(ss), resetop) - @test canonicalize!(ssr1)==canonicalize!(ssr2)==canonicalize!(ssro) - traceout!(ssr2,to_trace) - ssr2v = stabilizerview(ssr2) - c, x, z = canonicalize!(ssr2v, ranks=true) - @test canonicalize!(copy(ssr2v)[:,perm])[1:z] == canonicalize!(copy(newstate)) - @test canonicalize!(msr2v) == c[1:z] - # Compare different datastractures - @test canonicalize!(copy(stabilizerview(mdr1)))==canonicalize!(copy(stabilizerview(msr1)))==canonicalize!(ssr1[1:mdr1.rank]) + @testset "Qubit resets" begin + @test_throws DimensionMismatch reset_qubits!(S"XX YY",S"X",[1,2]) + @test_throws DimensionMismatch reset_qubits!(S"X",S"XX YY",[1,2]) + @test_throws DimensionMismatch reset_qubits!(MixedStabilizer(S"XX YY"),S"X",[1,2]) + @test_throws DimensionMismatch reset_qubits!(MixedStabilizer(S"X"),S"XX YY",[1,2]) + @test_throws DimensionMismatch reset_qubits!(MixedDestabilizer(S"XX YY"),S"X",[1,2]) + @test_throws DimensionMismatch reset_qubits!(MixedDestabilizer(S"X"),S"XX YY",[1,2]) + for N in test_sizes + for R in [rand(N÷2:N*2÷3), N] + R > 0 || continue + s = random_stabilizer(R,N) + nnew = rand(N÷4:N*2÷3) + nnew > 0 || continue + newstate = random_stabilizer(nnew) + perm = randperm(N)[1:nqubits(newstate)] + to_trace = setdiff(1:N,perm) + resetop = Reset(newstate, perm) + # Testing MixedDestabilizer + md = MixedDestabilizer(s) + mdr1 = reset_qubits!(copy(md), newstate,perm) + @test mixed_destab_looks_good(mdr1) + mdr2 = reset_qubits!(copy(mdr1),newstate,perm) + mdro = apply!(copy(md), resetop) + @test canonicalize!(copy(stabilizerview(mdr1)))==canonicalize!(copy(stabilizerview(mdr2)))==canonicalize!(copy(stabilizerview(mdro))) + traceout!(mdr2,to_trace) + mdr2v = stabilizerview(mdr2) + @test canonicalize!(copy(mdr2v)[:,perm]) == canonicalize!(copy(newstate)) + # Testing MixedStabilizer + ms = MixedStabilizer(s) + msr1 = reset_qubits!(copy(ms), newstate,perm) + @test mixed_stab_looks_good(msr1) + msr2 = reset_qubits!(copy(msr1),newstate,perm) + msro = apply!(copy(ms), resetop) + @test msr1==msr2==msro + traceout!(msr2,to_trace) + msr2v = stabilizerview(msr2) + @test canonicalize!(copy(msr2v)[:,perm]) == canonicalize!(copy(newstate)) + @test canonicalize!(msr2v) == canonicalize!(mdr2v) + # Testing Stabilizer + ss = R==N ? s : Stabilizer(tab(MixedStabilizer(s))) # Ensure the tableau is padded with Is + ssr1 = reset_qubits!(copy(ss), newstate,perm) + ssr2 = reset_qubits!(copy(ssr1),newstate,perm) + ssro = apply!(copy(ss), resetop) + @test canonicalize!(ssr1)==canonicalize!(ssr2)==canonicalize!(ssro) + traceout!(ssr2,to_trace) + ssr2v = stabilizerview(ssr2) + c, x, z = canonicalize!(ssr2v, ranks=true) + @test canonicalize!(copy(ssr2v)[:,perm])[1:z] == canonicalize!(copy(newstate)) + @test canonicalize!(msr2v) == c[1:z] + # Compare different datastractures + @test canonicalize!(copy(stabilizerview(mdr1)))==canonicalize!(copy(stabilizerview(msr1)))==canonicalize!(ssr1[1:mdr1.rank]) + end end end end From db6b01c49afc319752246b681529b8331d3686b8 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad <93876775+Fe-r-oz@users.noreply.github.com> Date: Sat, 3 Aug 2024 23:36:11 +0500 Subject: [PATCH 19/66] improving `doctests` for `Enumeration.jl` (#327) Co-authored-by: Stefan Krastanov --- src/enumeration.jl | 42 ++++++++++++++++++++++++++++++++++++++++-- test/test_enumerate.jl | 4 ++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/enumeration.jl b/src/enumeration.jl index a7850082d..771bf40a1 100644 --- a/src/enumeration.jl +++ b/src/enumeration.jl @@ -9,6 +9,18 @@ const all_single_qubit_patterns = ( """Generate a symbolic single-qubit gate given its index. Optionally, set non-trivial phases. +```jldoctest +julia> enumerate_single_qubit_gates(6) +sPhase on qubit 1 +X₁ ⟼ + Y +Z₁ ⟼ + Z + +julia> enumerate_single_qubit_gates(6, qubit=2, phases=(true, true)) +SingleQubitOperator on qubit 2 +X₁ ⟼ - Y +Z₁ ⟼ - Z +``` + See also: [`enumerate_cliffords`](@ref).""" function enumerate_single_qubit_gates(index; qubit=1, phases::Tuple{Bool,Bool}=(false,false)) @assert index<=6 "Only 6 single-qubit gates exit, up to the choice of phases" @@ -37,9 +49,21 @@ function enumerate_single_qubit_gates(index; qubit=1, phases::Tuple{Bool,Bool}=( end end -"""The size of the Clifford group over a given number of qubits, possibly modulo the phases. +"""The size of the Clifford group `𝒞` over a given number of qubits, possibly modulo the phases. + +For n qubits, not accounting for phases is `2ⁿⁿΠⱼ₌₁ⁿ(4ʲ-1)`. There are `4ⁿ` different phase configurations. + +```jldoctest +julia> clifford_cardinality(7) +457620995529680351512370381586432000 +``` -For n qubits, not accounting for phases is 2ⁿⁿΠⱼ₌₁ⁿ(4ʲ-1). There are 4ⁿ different phase configurations. +When not accounting for phases (`phases = false`) the result is the same as the size of the Symplectic group `Sp(2n) ≡ 𝒞ₙ/𝒫ₙ`, where `𝒫ₙ` is the Pauli group over `n` qubits. + +```jldoctest +julia> clifford_cardinality(7, phases=false) +27930968965434591767112450048000 +``` See also: [`enumerate_cliffords`](@ref). """ @@ -83,6 +107,20 @@ end The algorithm is detailed in [koenig2014efficiently](@cite). +```jldoctest +julia> symplecticGS(P"X", padded_n=3) +X₁ ⟼ + X__ +X₂ ⟼ + _X_ +X₃ ⟼ + __X +Z₁ ⟼ + Z__ +Z₂ ⟼ + _Z_ +Z₃ ⟼ + __Z + +julia> symplecticGS(P"Z") +X₁ ⟼ + Z +Z₁ ⟼ + X +``` + See also: [`enumerate_cliffords`](@ref), [`clifford_cardinality`](@ref).""" function symplecticGS(pauli::PauliOperator; padded_n=nqubits(pauli)) n = nqubits(pauli) diff --git a/test/test_enumerate.jl b/test/test_enumerate.jl index b1c0ac932..e9f3c7df3 100644 --- a/test/test_enumerate.jl +++ b/test/test_enumerate.jl @@ -4,4 +4,8 @@ @test length(collect(enumerate_cliffords(2))) == length(collect(enumerate_phases(enumerate_cliffords(2))))/2^4 == 720 @test first(enumerate_cliffords(3)) == C"X__ __Z _Z_ Z__ __X _X_" @test first(enumerate_cliffords(5)) == C"X____ ____Z ___Z_ __Z__ _Z___ Z____ ____X ___X_ __X__ _X___" + for n in 1:10 + symplectic_cardinality = clifford_cardinality(n, phases=false) + @test clifford_cardinality(n) == symplectic_cardinality*(2^(2n)) + end end From b02add9d0b3b54f068784fff2fc411446cfb6e38 Mon Sep 17 00:00:00 2001 From: IsaacP1234 <111547953+IsaacP1234@users.noreply.github.com> Date: Sat, 3 Aug 2024 15:36:15 -0400 Subject: [PATCH 20/66] Some group theory tools for Quantum error correction (#293) Co-authored-by: Kenneth Goodenough Co-authored-by: Stefan Krastanov Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 12 +- Project.toml | 2 +- src/QuantumClifford.jl | 3 + src/grouptableaux.jl | 211 ++++++++++++++++++++++++++++++++++++ src/pauli_operator.jl | 5 + src/project_trace_reset.jl | 21 ++++ test/test_group_tableaux.jl | 126 +++++++++++++++++++++ 7 files changed, 377 insertions(+), 3 deletions(-) create mode 100644 src/grouptableaux.jl create mode 100644 test/test_group_tableaux.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index a150277a0..bf99894df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,16 @@ # News -## v0.9.8 - 2024-07-24 - +## v0.9.8 - 2024-08-03 + +- New group-theoretical tools: + - `groupify` to generate full stabilizer group from generating set + - `minimal_generating_set` function to find the minimal generating set of a set of operators + - `pauligroup` to generate the full Pauli group of a certain number of qubits + - `normalizer` to generate all Paulis that commute with a set of Paulis + - `centralizer` to find a subset of a set of Paulis such that each element in the subset commutes with each element in the set + - `contractor` to find a subset of Paulis in a tableau that have an identity operator on a certain qubit + - `delete_columns` to remove the operators corresponding to a certain qubit from all Paulis in a Stabilizer - `PauliError` can now encode biased noise during Pauli frame simulation, i.e. one can simulate only X errors, or only Y errors, or only Z errors, or some weighted combination of these. ## v0.9.7 - 2024-07-23 diff --git a/Project.toml b/Project.toml index 196ddf832..550c83850 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.7" +version = "0.9.8" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 44fb23312..657710594 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -74,6 +74,8 @@ export single_z, single_x, single_y, # Graphs graphstate, graphstate!, graph_gatesequence, graph_gate, + # Group theory tools + groupify, minimal_generating_set, pauligroup, normalizer, centralizer, contractor, delete_columns, # Clipped Gauge canonicalize_clip!, bigram, entanglement_entropy, # mctrajectories @@ -1202,6 +1204,7 @@ include("sumtypes.jl") include("precompiles.jl") include("ecc/ECC.jl") include("nonclifford.jl") +include("grouptableaux.jl") include("plotting_extensions.jl") # include("gpu_adapters.jl") diff --git a/src/grouptableaux.jl b/src/grouptableaux.jl new file mode 100644 index 000000000..b0f76e1c2 --- /dev/null +++ b/src/grouptableaux.jl @@ -0,0 +1,211 @@ +using Graphs +using LinearAlgebra + +""" +Return the full stabilizer group represented by the input generating set (a [`Stabilizer`](@ref)). + +The returned object is exponentially long. + +```jldoctest +julia> groupify(S"XZ ZX") ++ __ ++ XZ ++ ZX ++ YY +``` +""" +function groupify(s::Stabilizer) + # Create a `Tableau` of 2ⁿ n-qubit identity Pauli operators(where n is the size of + # `Stabilizer` s), then multiply each one by a different subset of the elements in s to + # create all 2ⁿ unique elements in the group generated by s, then return the `Tableau`. + n = length(s)::Int + group = zero(Tableau, 2^n, nqubits(s)) + for i in 0:2^n-1 + for (digit_order, j) in enumerate(digits(i, base=2, pad=n)) + if j == 1 + group[i+1] *= s[digit_order] + end + end + end + return group +end + + +""" +For a not-necessarily-minimal generating set, +return the minimal generating set. + +The input has to have only real phases. + +```jldoctest +julia> minimal_generating_set(S"__ XZ ZX YY") ++ XZ ++ ZX +``` +""" +function minimal_generating_set(s::Stabilizer) + # Canonicalize `Stabilizer` s, then return a `Stabilizer` with all non-identity Pauli operators + # in the result. If s consists of only identity operators, return the negative + # identity operator if one is contained in s, and the positive identity operator otherwise. + s, _, r = canonicalize!(copy(s), ranks=true) + if r == 0 + gs = zero(Stabilizer, 1, nqubits(s)) + if 0x02 in phases(s) + gs[1] = -1 * gs[1] + end + return gs + else + return s[1:r, :] + end +end + +""" +Return the full Pauli group of a given length. Phases are ignored by default, +but can be included by setting `phases=true`. + +```jldoctest +julia> pauligroup(1) ++ _ ++ X ++ Z ++ Y + +julia> pauligroup(1, phases=true) ++ _ ++ X ++ Z ++ Y +- _ +- X +- Z +- Y ++i_ ++iX ++iZ ++iY +-i_ +-iX +-iZ +-iY +``` +""" +function pauligroup(n::Int; phases=false) + if phases + s = zero(Tableau, 4^(n + 1), n) + paulis = ((false, false), (true, false), (false, true), (true, true)) + for (i, P) in enumerate(Iterators.product(Iterators.repeated(paulis, n)...)) + for (j, p) in enumerate(P) + s[i, j] = p + end + end + for i in 1:4^n + s[i+4^n] = -1 * s[i] + end + for i in 4^n+1:2*4^n + s[i+4^n] = -1im * s[i] + end + for i in 2*4^n+1:3*4^n + s[i+4^n] = -1 * s[i] + end + end + if !phases + s = zero(Tableau, 4^n, n) + paulis = ((false, false), (true, false), (false, true), (true, true)) + for (i, P) in enumerate(Iterators.product(Iterators.repeated(paulis, n)...)) + for (j, p) in enumerate(P) + s[i, j] = p + end + end + end + return s +end + +""" +Return all Pauli operators with the same number of qubits as the given `Tableau` `t` +that commute with all operators in `t`. + +```jldoctest +julia> normalizer(T"X") ++ _ ++ X +``` +""" +function normalizer(t::Tableau) + # For each `PauliOperator` p in the with same number of qubits as the `Stabilizer` s, iterate through s and check each + # operator's commutivity with p. If they all commute, add p a vector of `PauliOperators`. Return the vector + # converted to `Tableau`. + n = nqubits(t) + pgroup = pauligroup(n, phases=false) + ptype = typeof(t[1]) + normalizer = ptype[] + for p in pgroup + commutes = true + for q in t + if comm(p, q) == 0x01 + commutes = false + end + end + if commutes + push!(normalizer, p) + end + end + + return Tableau(normalizer) +end + +""" +For a given set of Paulis (in the form of a `Tableau`), return the subset of Paulis that commute with all Paulis in set. + +```jldoctest +julia> centralizer(T"XX ZZ _Z") ++ ZZ +``` +""" +function centralizer(t::Tableau) + center = typeof(t[1])[] + for P in t + commutes = 0 + for Q in t + if comm(P, Q) == 0x01 + commutes = 1 + break + end + end + if commutes == 0 + push!(center, P) + end + end + if length(center) == 0 + return Tableau(zeros(Bool, 1,1)) + end + c = Tableau(center) + return c +end + +""" +Return the subset of Paulis in a Stabilizer that have identity operators on all qubits corresponding to +the given subset, without the entries corresponding to subset. + +```jldoctest +julia> contractor(S"_X X_", [1]) ++ X +``` +""" +function contractor(s::Stabilizer, subset) + result = typeof(s[1])[] + for p in s + contractable = true + for i in subset + if p[i] != (false, false) + contractable = false + break + end + end + if contractable push!(result, p[setdiff(1:length(p), subset)]) end + end + if length(result) > 0 + return Tableau(result) + else + return Tableau(zeros(Bool, 1,1)) + end +end \ No newline at end of file diff --git a/src/pauli_operator.jl b/src/pauli_operator.jl index 640092b55..f607ab07a 100644 --- a/src/pauli_operator.jl +++ b/src/pauli_operator.jl @@ -122,6 +122,11 @@ Base.hash(p::PauliOperator, h::UInt) = hash(p.phase,hash(p.nqubits,hash(p.xz, h) Base.copy(p::PauliOperator) = PauliOperator(copy(p.phase),p.nqubits,copy(p.xz)) +function Base.deleteat!(p::PauliOperator, subset) + p =p[setdiff(1:length(p), subset)] + return p +end + _nchunks(i::Int,T::Type{<:Unsigned}) = 2*( (i-1) ÷ (8*sizeof(T)) + 1 ) Base.zero(::Type{PauliOperator{Tₚ, Tᵥ}}, q) where {Tₚ,T<:Unsigned,Tᵥ<:AbstractVector{T}} = PauliOperator(zeros(UInt8), q, zeros(T, _nchunks(q,T))) Base.zero(::Type{PauliOperator}, q) = zero(PauliOperator{Array{UInt8, 0}, Vector{UInt}}, q) diff --git a/src/project_trace_reset.jl b/src/project_trace_reset.jl index 8ea01f38c..19e49521b 100644 --- a/src/project_trace_reset.jl +++ b/src/project_trace_reset.jl @@ -627,6 +627,8 @@ end $TYPEDSIGNATURES Trace out a qubit. + +See also: [`delete_columns`](@ref) """ # TODO all of these should raise an error if length(qubits)>rank function traceout!(s::Stabilizer, qubits; phases=true, rank=false) _,i = canonicalize_rref!(s,qubits;phases=phases) @@ -866,3 +868,22 @@ function traceoutremove!(s::MixedDestabilizer, qubit) traceout!(s,[qubit]) # TODO this can be optimized thanks to the information already known from projfunc s = _remove_rowcol!(s, nqubits(s), qubit) end + + +""" +Return the given stabilizer without all the qubits in the given iterable. + +The resulting tableaux is not guaranteed to be valid (to retain its commutation relationships). + +```jldoctest +julia> delete_columns(S"XYZ YZX ZXY", [1,3]) ++ Y ++ Z ++ X +``` + +See also: [`traceout!`](@ref) +""" +function delete_columns(𝒮::Stabilizer, subset) + return 𝒮[:, setdiff(1:nqubits(𝒮), subset)] +end \ No newline at end of file diff --git a/test/test_group_tableaux.jl b/test/test_group_tableaux.jl new file mode 100644 index 000000000..afec47e90 --- /dev/null +++ b/test/test_group_tableaux.jl @@ -0,0 +1,126 @@ +@testitem "Classical" begin + using Test + + using Random + using QuantumClifford + + # Including sizes that would test off-by-one errors in the bit encoding. + test_sizes = [1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17] + # Zero function(in groupify) slows down around 2^30(n=30),eventually breaks + small_test_sizes = [1, 2, 3, 4, 5, 7] # Pauligroup slows around n = 8 + + @testset "group_tableaux" begin + #Test groupify + for n in [1, test_sizes...] + s = random_stabilizer(n) + s_test = copy(s) + group = groupify(s) + @test length(group) == 2^n + unchanged = true + for stabilizer in group + apply!(s, stabilizer) + if !(s == s_test) + unchanged = false + end + @test unchanged == true + end + end + #Test minimal_generating_set + for n in [1, small_test_sizes...] + s = random_stabilizer(n) + group = groupify(s) + gen_set = minimal_generating_set(Stabilizer(group)) + new_group = groupify(gen_set) + canonicalize!(Stabilizer(group)) + canonicalize!(Stabilizer(new_group)) + @test group == new_group + s = zero(Stabilizer, rand(1:(2*n)), n) + for i in 1:length(s) + s[i] = random_pauli(n) + end + gen_set = minimal_generating_set(s) + new_group = groupify(s) + for operator in s + @test operator in new_group + end + end + #Test pauligroup + for n in [1, small_test_sizes...] + @test length(QuantumClifford.pauligroup(n, phases=false)) == 4^n + @test length(QuantumClifford.pauligroup(n, phases=true)) == 4^(n+1) + end + #Test normalizer + for n in [1, small_test_sizes...] # pauligroup is very slow at n=14 + t = zero(QuantumClifford.Tableau, rand(1:(2*n)), n) + for i in eachindex(t) t[i] = random_pauli(n) end + normalized = normalizer(t) + paulis = QuantumClifford.pauligroup(n, phases=false) + for n_pauli in normalized + for t_pauli in t + @test comm(n_pauli, t_pauli) == 0x0 + end + end + for pauli in paulis + commutes = true + for t_pauli in t + if comm(t_pauli, pauli) == 0x01 + commutes = false + end + end + @test (!commutes) || (pauli in normalized) + end + end + #Test centralizer + for n in [1, test_sizes...] + t = zero(QuantumClifford.Tableau, rand(1:(2*n)), n) + for i in eachindex(t) t[i] = random_pauli(n) end + c = centralizer(t) + for c_pauli in c + for t_pauli in t + @test comm(c_pauli, t_pauli) == 0x0 + end + end + for pauli in t + commutes = true + for t_pauli in t + if comm(t_pauli, pauli)==0x01 commutes = false end + end + @test !commutes || pauli in c + end + + end + #Test contractor + for n in [1, test_sizes...] + s = random_stabilizer(n) + subset = [] + for i in 1:nqubits(s) #create a random subset + if rand(1:2) == 1 push!(subset, i) end + end + c = contractor(s, subset) + count = 0 + for stabilizer in s + contractable = true + for i in subset + if stabilizer[i] != (false, false) contractable = false end + end + if contractable count+=1 end + end + if length(c[1]) > 0 + @test count == length(c) + for contracted in c + p = zero(PauliOperator, nqubits(s)) + index = 0 + for i in 1:nqubits(s) + if !(i in subset) + index+=1 + p[i] = contracted[index] + end + end + @test p in s || -1* p in s || 1im * p in s || -1im * p in s + end + else + @test count ==0 + end + end + end +end \ No newline at end of file From 12924ec8b665a13bb229d1e10cbdb6977dd3c883 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Sat, 3 Aug 2024 18:59:24 -0400 Subject: [PATCH 21/66] CI on ARM Mac (#269) --- .github/workflows/ci.yml | 13 +++++++++++++ test/runtests.jl | 4 ++++ test/test_bitpack.jl | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5750c1529..e00b68835 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,19 @@ jobs: - '5' arch: - x64 + include: + - arch: aarch64 + os: macos-latest + version: '1' + threads: '1' + - arch: x64 + os: macos-latest + version: '1' + threads: '1' + - arch: x64 + os: windows-latest + version: '1' + threads: '1' steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v1 diff --git a/test/runtests.jl b/test/runtests.jl index 9d1ee89e9..c47080c2f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -20,6 +20,10 @@ testfilter = ti -> begin push!(exclude, :gpu) end + if !(Base.Sys.islinux() & (Int===Int64)) + push!(exclude, :bitpack) + end + return all(!in(exclude), ti.tags) end diff --git a/test/test_bitpack.jl b/test/test_bitpack.jl index d56056d61..e5aae358c 100644 --- a/test/test_bitpack.jl +++ b/test/test_bitpack.jl @@ -1,4 +1,4 @@ -@testitem "Alternative bit packing" begin +@testitem "Alternative bit packing" tags=[:bitpack] begin using Random using QuantumClifford: Tableau From dad8484bcc8f5da40c317868b5926986eb686f76 Mon Sep 17 00:00:00 2001 From: Tommy Hofmann Date: Sun, 4 Aug 2024 02:21:18 +0200 Subject: [PATCH 22/66] Make doctest inside @testitem work (#331) Co-authored-by: Stefan Krastanov --- docs/src/ecc_example_sim.md | 1 - docs/src/noisycircuits.md | 1 - docs/src/noisycircuits_mc.md | 1 - docs/src/noisycircuits_ops.md | 1 - docs/src/noisycircuits_perturb.md | 1 - 5 files changed, 5 deletions(-) diff --git a/docs/src/ecc_example_sim.md b/docs/src/ecc_example_sim.md index 18b2c6f35..4658379d1 100644 --- a/docs/src/ecc_example_sim.md +++ b/docs/src/ecc_example_sim.md @@ -5,7 +5,6 @@ DocTestSetup = quote using QuantumClifford using Quantikz end -CurrentModule = QuantumClifford.Experimental.NoisyCircuits ``` !!! warning "The documentation is incomplete" diff --git a/docs/src/noisycircuits.md b/docs/src/noisycircuits.md index e0ef62ff0..ed03bbaa2 100644 --- a/docs/src/noisycircuits.md +++ b/docs/src/noisycircuits.md @@ -5,7 +5,6 @@ DocTestSetup = quote using QuantumClifford using QuantumClifford.Experimental.NoisyCircuits end -CurrentModule = QuantumClifford.Experimental.NoisyCircuits ``` !!! warning "Unstable" diff --git a/docs/src/noisycircuits_mc.md b/docs/src/noisycircuits_mc.md index 5cccea237..37e900df4 100644 --- a/docs/src/noisycircuits_mc.md +++ b/docs/src/noisycircuits_mc.md @@ -6,7 +6,6 @@ DocTestSetup = quote using QuantumClifford.Experimental.NoisyCircuits using Quantikz end -CurrentModule = QuantumClifford.Experimental.NoisyCircuits ``` !!! warning "Unstable" diff --git a/docs/src/noisycircuits_ops.md b/docs/src/noisycircuits_ops.md index 03186b45b..9d45c3f86 100644 --- a/docs/src/noisycircuits_ops.md +++ b/docs/src/noisycircuits_ops.md @@ -6,7 +6,6 @@ DocTestSetup = quote using QuantumClifford.Experimental.NoisyCircuits using Quantikz end -CurrentModule = QuantumClifford.Experimental.NoisyCircuits ``` !!! warning "Unstable" diff --git a/docs/src/noisycircuits_perturb.md b/docs/src/noisycircuits_perturb.md index 87e76ecf5..54866ae2b 100644 --- a/docs/src/noisycircuits_perturb.md +++ b/docs/src/noisycircuits_perturb.md @@ -6,7 +6,6 @@ DocTestSetup = quote using QuantumClifford.Experimental.NoisyCircuits using Quantikz end -CurrentModule = QuantumClifford.Experimental.NoisyCircuits ``` !!! warning "Unstable" From 263195ca43a51b5f67d0496e3c3183558ec112fa Mon Sep 17 00:00:00 2001 From: Feroz Ahmad <93876775+Fe-r-oz@users.noreply.github.com> Date: Mon, 5 Aug 2024 22:58:05 +0500 Subject: [PATCH 23/66] `inv` implementation for `TwoQubitOperator` (#315) Co-authored-by: Fe-r-oz Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- src/QuantumClifford.jl | 2 +- src/misc_ops.jl | 4 ++++ src/noise.jl | 5 +---- src/pauli_operator.jl | 8 +++++++- src/symbolic_cliffords.jl | 16 ++++++++++++++++ test/test_noisycircuits.jl | 18 ++++++++++++++++-- test/test_paulis.jl | 5 +++++ test/test_symcliff.jl | 13 +++++++++++++ 10 files changed, 68 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf99894df..604e855c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ # News +## v0.9.9 - 2024-08-05 + +- `inv` is implemented for all Clifford operator types (symbolic, dense, sparse). + ## v0.9.8 - 2024-08-03 - New group-theoretical tools: diff --git a/Project.toml b/Project.toml index 550c83850..a5dc91bfb 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.8" +version = "0.9.9" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 657710594..d54ec03b6 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -50,7 +50,7 @@ export sHadamard, sPhase, sInvPhase, SingleQubitOperator, sId1, sX, sY, sZ, sCNOT, sCPHASE, sSWAP, sXCX, sXCY, sXCZ, sYCX, sYCY, sYCZ, sZCX, sZCY, sZCZ, - sZCrY, + sZCrY, sInvZCrY, # Misc Ops SparseGate, sMX, sMY, sMZ, PauliMeasurement, Reset, sMRX, sMRY, sMRZ, diff --git a/src/misc_ops.jl b/src/misc_ops.jl index bf26489e0..a48ff70fc 100644 --- a/src/misc_ops.jl +++ b/src/misc_ops.jl @@ -32,6 +32,10 @@ function apply!(state::AbstractStabilizer, g::SparseGate; kwargs...) apply!(state, g.cliff, g.indices; kwargs...) end +function LinearAlgebra.inv(g::SparseGate; phases=true) + return SparseGate(inv(g.cliff;phases=phases), g.indices) +end + """Reset the specified qubits to the given state. Be careful, this operation implies first tracing out the qubits, which can lead to mixed states diff --git a/src/noise.jl b/src/noise.jl index 8398e032e..226a77780 100644 --- a/src/noise.jl +++ b/src/noise.jl @@ -34,6 +34,7 @@ struct PauliNoise{T} <: AbstractNoise py::T pz::T end + function PauliNoise(px::Real, py::Real, pz::Real) px, py, pz = float.((px, py, pz)) px, py, pz = promote(px, py, pz) @@ -41,10 +42,6 @@ function PauliNoise(px::Real, py::Real, pz::Real) return PauliNoise{T}(px, py, pz) end -"""A convenient constructor for various types of Pauli noise models. -Returns more specific types when necessary.""" -function PauliNoise end - """Constructs an unbiased Pauli noise model with total probability of error `p`.""" function PauliNoise(p) UnbiasedUncorrelatedNoise(p) diff --git a/src/pauli_operator.jl b/src/pauli_operator.jl index f607ab07a..e556628bc 100644 --- a/src/pauli_operator.jl +++ b/src/pauli_operator.jl @@ -122,7 +122,13 @@ Base.hash(p::PauliOperator, h::UInt) = hash(p.phase,hash(p.nqubits,hash(p.xz, h) Base.copy(p::PauliOperator) = PauliOperator(copy(p.phase),p.nqubits,copy(p.xz)) -function Base.deleteat!(p::PauliOperator, subset) +function LinearAlgebra.inv(p::PauliOperator) + ph = p.phase[] + phin = xor((ph << 1) & ~(UInt8(1) << 2), ph) + return PauliOperator(phin, p.nqubits, copy(p.xz)) +end + +function Base.deleteat!(p::PauliOperator, subset) p =p[setdiff(1:length(p), subset)] return p end diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index 0d2c26c40..38be9eeb4 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -301,6 +301,7 @@ end @qubitop2 YCZ (x1⊻x2 , x2⊻z1 , x2 , z2⊻x1⊻z1, ~iszero( (x2 & (x1 ⊻ z1) & (z2 ⊻ x1)) )) @qubitop2 ZCrY (x1, x1⊻z1⊻x2⊻z2, x1⊻x2, x1⊻z2, ~iszero((x1 & ~z1 & x2) | (x1 & ~z1 & ~z2) | (x1 & x2 & ~z2))) +@qubitop2 InvZCrY (x1, x1⊻z1⊻x2⊻z2, x1⊻x2, x1⊻z2, ~iszero((x1 & z1 & ~x2 & ~z2) | (x1 & ~z1 & ~x2 & z2) | (x1 & z1 & ~x2 & z2) | (x1 & z1 & x2 & z2))) #= To get the boolean formulas for the phase, it is easiest to first write down the truth table for the phase: @@ -346,6 +347,21 @@ function Base.show(io::IO, op::AbstractTwoQubitOperator) end end +LinearAlgebra.inv(op::sSWAP) = sSWAP(op.q1, op.q2) +LinearAlgebra.inv(op::sCNOT) = sCNOT(op.q1, op.q2) +LinearAlgebra.inv(op::sCPHASE) = sCPHASE(op.q1, op.q2) +LinearAlgebra.inv(op::sZCX) = sZCX(op.q1, op.q2) +LinearAlgebra.inv(op::sZCY) = sZCY(op.q1, op.q2) +LinearAlgebra.inv(op::sZCZ) = sZCZ(op.q1, op.q2) +LinearAlgebra.inv(op::sXCX) = sXCX(op.q1, op.q2) +LinearAlgebra.inv(op::sXCY) = sXCY(op.q1, op.q2) +LinearAlgebra.inv(op::sXCZ) = sXCZ(op.q1, op.q2) +LinearAlgebra.inv(op::sYCX) = sYCX(op.q1, op.q2) +LinearAlgebra.inv(op::sYCY) = sYCY(op.q1, op.q2) +LinearAlgebra.inv(op::sYCZ) = sYCZ(op.q1, op.q2) +LinearAlgebra.inv(op::sZCrY) = sInvZCrY(op.q1, op.q2) +LinearAlgebra.inv(op::sInvZCrY) = sZCrY(op.q1, op.q2) + ############################## # Functions that perform direct application of common operators without needing an operator instance ############################## diff --git a/test/test_noisycircuits.jl b/test/test_noisycircuits.jl index 22a4b09bf..8ae674161 100644 --- a/test/test_noisycircuits.jl +++ b/test/test_noisycircuits.jl @@ -5,8 +5,16 @@ test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. using QuantumClifford.Experimental.NoisyCircuits + import AbstractAlgebra + @testset "SparseGate" begin + g = SparseGate(random_clifford(2), randperm(10)[1:2]) + gi = inv(g) + c = random_stabilizer(10) + @assert apply!(apply!(copy(c), g), gi) == c + end + @testset "Noisy Gates" begin g1 = SparseGate(tId1, [1]) g2 = SparseGate(tCNOT, [2,3]) @@ -25,6 +33,7 @@ resp = petrajectories(copy(state), [ng1,ng2,ng3,ng4,ng5]) @test all(values(resp).==0) end + @testset "Monte Carlo Purification examples" begin g1 = SparseGate(tCNOT, [1,3]) g2 = SparseGate(tCNOT, [2,4]) @@ -49,8 +58,6 @@ @test nonoise[true_success_stat] == 10 end - - @testset "Perturbative expansion Purification examples" begin @testset "Comparison to MC" begin compare(a,b, symbol) = abs(a[symbol]/500-b[symbol]) / (a[symbol]/500+b[symbol]+1e-5) < 0.3 @@ -79,6 +86,7 @@ @test compare(mc,pe,false_success_stat) @test compare(mc,pe,true_success_stat) end + @testset "Symbolic" begin R, (e,) = AbstractAlgebra.polynomial_ring(AbstractAlgebra.RealField, ["e"]) unity = R(1); @@ -97,6 +105,7 @@ @test pe_symbolic[true_success_stat] == 27.0*e^4 + -54.0*e^3 + 36.0*e^2 + -10.0*e + 1.0 end end + @testset "Measurements" begin @testset "BellMeasurements" begin stateX = S"X" @@ -138,6 +147,7 @@ @test random2_pe[failure_stat]+random2_pe[false_success_stat] == 1 @test random2_pe[true_success_stat] == 0 end + @testset "PauliMeasurements" begin ghzState = S"XXX ZZI @@ -174,6 +184,7 @@ @test random2_pe[false_success_stat] == 1 @test random2_pe[true_success_stat] == 0 end + @testset "Sparse Measurements" begin ghzState = S"XXX ZZI @@ -212,6 +223,7 @@ @test random2_pe[false_success_stat] == 1 @test random2_pe[true_success_stat] == 0 end + @testset "Conforming to the project! interface" begin state = Register(MixedDestabilizer(S"ZZ"), zeros(Bool, 1)) meas = PauliMeasurement(P"ZI", 1) @@ -222,6 +234,7 @@ ZI" end end + @testset "Classical Bits" begin @testset "DecisionGate" begin X_error = CliffordOperator([P"X", P"-Z"]) @@ -256,6 +269,7 @@ canonicalize!(quantumstate(r)) @test stabilizerview(r) == expectedFinalState end + @testset "ConditionalGate" begin id_op = CliffordOperator([P"X", P"Z"]) X_error = CliffordOperator([P"X", P"-Z"]) diff --git a/test/test_paulis.jl b/test/test_paulis.jl index 778282706..c53c7d847 100644 --- a/test/test_paulis.jl +++ b/test/test_paulis.jl @@ -38,6 +38,11 @@ @test prodphase(P"XX",P"YY") == 0x2 @test prodphase(P"ZZZ",P"XXX") == prodphase(S"III ZZZ",P"XXX",2) == prodphase(P"ZZZ",S"III XXX",2) == prodphase(S"III ZZZ",S"III XXX",2,2) == 0x3 end + + for Pop in [P"X", P"iX", P"-iXYZ", random_pauli(100; nophase=false, realphase=false)] + @test Pop * inv(Pop) == zero(Pop) + end + @testset "Commutation implies real phase" begin for i in 1:10 for n in test_sizes diff --git a/test/test_symcliff.jl b/test/test_symcliff.jl index 66ea60154..753d82841 100644 --- a/test/test_symcliff.jl +++ b/test/test_symcliff.jl @@ -77,4 +77,17 @@ @test CliffordOperator(inv(SingleQubitOperator(random_op)), i) == inv(CliffordOperator(random_op, i)) end end + + @testset "TwoQubitOperator inv methods" begin + for gate_type in subtypes(QuantumClifford.AbstractTwoQubitOperator) + n₁ = rand(2: 10) + n₂ = rand(1:(n₁ - 1)) + @test CliffordOperator(inv(gate_type(n₁, n₂)), n₁) == inv(CliffordOperator(gate_type(n₁, n₂), n₁)) + @test CliffordOperator(inv(gate_type(n₂, n₁)), n₁) == inv(CliffordOperator(gate_type(n₂, n₁), n₁)) + @test CliffordOperator(inv(sZCX(n₁, n₂)), n₁) == inv(CliffordOperator(sCNOT(n₁, n₂), n₁)) + @test CliffordOperator(inv(sXCZ(n₁, n₂)), n₁) == inv(CliffordOperator(sCNOT(n₂, n₁), n₁)) + @test CliffordOperator(inv(sZCrY(n₁, n₂)), n₁) == inv(CliffordOperator(sZCrY(n₁, n₂), n₁)) + @test CliffordOperator(inv(sInvZCrY(n₁, n₂)), n₁) == inv(CliffordOperator(sInvZCrY(n₁, n₂), n₁)) + end + end end From e51a142d8c6b8300cfa1b417b677c7e349a1bb0b Mon Sep 17 00:00:00 2001 From: Adriano Meligrana <68152031+Tortar@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:07:25 +0200 Subject: [PATCH 24/66] Solve piracies and ambiguities (#334) --- src/misc_ops.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/misc_ops.jl b/src/misc_ops.jl index a48ff70fc..8587e28ed 100644 --- a/src/misc_ops.jl +++ b/src/misc_ops.jl @@ -1,3 +1,5 @@ +import QuantumInterface: nsubsystems + """A Stabilizer measurement on the entirety of the quantum register. `projectrand!(state, pauli)` and `apply!(state, PauliMeasurement(pauli))` give the same (possibly non-deterministic) result. @@ -17,6 +19,9 @@ function apply!(state::AbstractStabilizer, m::PauliMeasurement) state end +function apply!(state::MixedDestabilizer, indices::Base.AbstractVecOrTuple, operation::Type{<:AbstractSymbolicOperator}) + apply!(state, operation(indices...)) +end """A Clifford gate, applying the given `cliff` operator to the qubits at the selected `indices`. @@ -140,3 +145,5 @@ struct ClassicalXOR{N} <: AbstractOperation end ClassicalXOR(bits,store) = ClassicalXOR{length(bits)}(tuple(bits...),store) + +nsubsystems(state::MixedDestabilizer) = nqubits(state) From da60debf9a86df4095de8de358a6ed40279ef6b5 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 12 Sep 2024 09:47:54 -0400 Subject: [PATCH 25/66] simplify JET (#358) --- test/test_jet.jl | 41 ++++++++++++----------------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/test/test_jet.jl b/test/test_jet.jl index ca8e8d9b1..ee8817840 100644 --- a/test/test_jet.jl +++ b/test/test_jet.jl @@ -6,34 +6,17 @@ using Graphs using StridedViews using LinearAlgebra -using JET: ReportPass, BasicPass, InferenceErrorReport, UncaughtExceptionReport - -# Custom report pass that ignores `UncaughtExceptionReport` -# Too coarse currently, but it serves to ignore the various -# "may throw" messages for runtime errors we raise on purpose -# (mostly on malformed user input) -struct MayThrowIsOk <: ReportPass end - -# ignores `UncaughtExceptionReport` analyzed by `JETAnalyzer` -(::MayThrowIsOk)(::Type{UncaughtExceptionReport}, @nospecialize(_...)) = return - -# forward to `BasicPass` for everything else -function (::MayThrowIsOk)(report_type::Type{<:InferenceErrorReport}, @nospecialize(args...)) - BasicPass()(report_type, args...) -end - - rep = report_package("QuantumClifford"; - report_pass=MayThrowIsOk(), - ignored_modules=( - AnyFrameModule(Graphs.LinAlg), - AnyFrameModule(Graphs.SimpleGraphs), - AnyFrameModule(ArrayInterface), - AnyFrameModule(Static), - AnyFrameModule(StridedViews), - AnyFrameModule(LinearAlgebra), - ) +rep = report_package("QuantumClifford"; + ignored_modules=( + AnyFrameModule(Graphs.LinAlg), + AnyFrameModule(Graphs.SimpleGraphs), + AnyFrameModule(ArrayInterface), + AnyFrameModule(Static), + AnyFrameModule(StridedViews), + AnyFrameModule(LinearAlgebra), ) - @show rep - @test_broken length(JET.get_reports(rep)) == 0 - @test length(JET.get_reports(rep)) <= 28 +) +@show rep +@test_broken length(JET.get_reports(rep)) == 0 +@test length(JET.get_reports(rep)) <= 23 end From d79d142ad100b7c0838128ac2aa06a071b270ce8 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Sat, 14 Sep 2024 08:32:28 +0500 Subject: [PATCH 26/66] Bug fix to the `parity_checks(ReedMuller(r, m))` along with `RecursiveReedMuller` code for cross-reference (#277) --------- Co-authored-by: Fe-r-oz Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 5 + src/ecc/ECC.jl | 7 +- .../codes/classical/recursivereedmuller.jl | 64 +++++++ src/ecc/codes/classical/reedmuller.jl | 52 ++++-- test/test_ecc_reedmuller.jl | 164 ++++++++++++------ test/test_ecc_throws.jl | 8 +- 6 files changed, 232 insertions(+), 68 deletions(-) create mode 100644 src/ecc/codes/classical/recursivereedmuller.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 604e855c9..6d46dc8c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ # News +## dev + +- **(fix)** Bug fix to the `parity_checks(ReedMuller(r, m))` of classical Reed-Muller code (it was returning generator matrix). +- `RecursiveReedMuller` code implementation as an alternative implementation of `ReedMuller`. + ## v0.9.9 - 2024-08-05 - `inv` is implemented for all Clifford operator types (symbolic, dense, sparse). diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index ed50dba5b..5498f2ad9 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -1,6 +1,7 @@ module ECC using LinearAlgebra +using LinearAlgebra: I using QuantumClifford using QuantumClifford: AbstractOperation, AbstractStabilizer, Stabilizer import QuantumClifford: Stabilizer, MixedDestabilizer, nqubits @@ -8,7 +9,7 @@ using DocStringExtensions using Combinatorics: combinations using SparseArrays: sparse using Statistics: std -using Nemo: ZZ, residue_ring, matrix, finite_field, GF, minpoly, coeff, lcm, FqPolyRingElem, FqFieldElem, is_zero, degree, defining_polynomial, is_irreducible +using Nemo: ZZ, residue_ring, matrix, finite_field, GF, minpoly, coeff, lcm, FqPolyRingElem, FqFieldElem, is_zero, degree, defining_polynomial, is_irreducible, echelon_form abstract type AbstractECC end @@ -70,6 +71,9 @@ In a [polynomial code](https://en.wikipedia.org/wiki/Polynomial_code), the gener """ function generator_polynomial end +"""The generator matrix of a code.""" +function generator end + parity_checks(s::Stabilizer) = s Stabilizer(c::AbstractECC) = parity_checks(c) MixedDestabilizer(c::AbstractECC; kwarg...) = MixedDestabilizer(Stabilizer(c); kwarg...) @@ -363,4 +367,5 @@ include("codes/concat.jl") include("codes/random_circuit.jl") include("codes/classical/reedmuller.jl") include("codes/classical/bch.jl") +include("codes/classical/recursivereedmuller.jl") end #module diff --git a/src/ecc/codes/classical/recursivereedmuller.jl b/src/ecc/codes/classical/recursivereedmuller.jl new file mode 100644 index 000000000..ae30ae578 --- /dev/null +++ b/src/ecc/codes/classical/recursivereedmuller.jl @@ -0,0 +1,64 @@ +""" +A construction of the Reed-Muller class of codes using the recursive definition. + +The Plotkin `(u, u + v)` construction defines a recursive relation between generator matrices of Reed-Muller `(RM)` codes [abbe2020reed](@cite). To derive the generator matrix `G(m, r)` for `RM(r, m)`, the generator matrices of lower-order codes are utilized: +- `G(r - 1, m - 1)`: Generator matrix of `RM(r - 1, m - 1)` +- `G(r, m - 1)`: Generator matrix of `RM(r, m - 1)` + +The generator matrix `G(m, r)` of `RM(m, r)` is formulated as follows in matrix notation: + +```math +G(m, r) = \begin{bmatrix} +G(r, m - 1) & G(r, m - 1) \\ +0 & G(r - 1, m - 1) +\end{bmatrix} +``` + +Here, the matrix 0 denotes an all-zero matrix with dimensions matching `G(r - 1, m - 1)`. This recursive approach facilitates the construction of higher-order Reed-Muller codes based on the generator matrices of lower-order codes. + +In addition, the dimension of `RM(m - r - 1, m)` equals the dimension of the dual of `RM(r, m)`. Thus, `RM(m - r - 1, m) = RM(r, m)^⊥` shows that the [dual code](https://en.wikipedia.org/wiki/Dual_code) of `RM(r, m)` is `RM(m − r − 1, m)`, indicating the parity check matrix of `RM(r, m)` is the generator matrix for `RM(m - r - 1, m)`. + +See also: `ReedMuller` +""" +struct RecursiveReedMuller <: ClassicalCode + r::Int + m::Int + + function RecursiveReedMuller(r, m) + if r < 0 || r > m + throw(ArgumentError("Invalid parameters: r must be non-negative and r ≤ m in order to valid code.")) + end + new(r, m) + end +end + +function _recursiveReedMuller(r::Int, m::Int) + if r == 1 && m == 1 + return Matrix{Int}([1 1; 0 1]) + elseif r == m + return Matrix{Int}(I, 2^m, 2^m) + elseif r == 0 + return Matrix{Int}(ones(1, 2^m)) + else + Gᵣₘ₋₁ = _recursiveReedMuller(r, m - 1) + Gᵣ₋₁ₘ₋₁ = _recursiveReedMuller(r - 1, m - 1) + return vcat(hcat(Gᵣₘ₋₁, Gᵣₘ₋₁), hcat(zeros(Int, size(Gᵣ₋₁ₘ₋₁)...), Gᵣ₋₁ₘ₋₁)) + end +end + +function generator(c::RecursiveReedMuller) + return _recursiveReedMuller(c.r, c.m) +end + +function parity_checks(c::RecursiveReedMuller) + H = generator(RecursiveReedMuller(c.m - c.r - 1, c.m)) + return H +end + +code_n(c::RecursiveReedMuller) = 2 ^ c.m + +code_k(c::RecursiveReedMuller) = sum(binomial.(c.m, 0:c.r)) + +distance(c::RecursiveReedMuller) = 2 ^ (c.m - c.r) + +rate(c::RecursiveReedMuller) = code_k(c::RecursiveReedMuller) / code_n(c::RecursiveReedMuller) diff --git a/src/ecc/codes/classical/reedmuller.jl b/src/ecc/codes/classical/reedmuller.jl index 7f9c1f668..eea827661 100644 --- a/src/ecc/codes/classical/reedmuller.jl +++ b/src/ecc/codes/classical/reedmuller.jl @@ -1,38 +1,60 @@ """The family of Reed-Muller codes, as discovered by Muller in his 1954 paper [muller1954application](@cite) and Reed who proposed the first efficient decoding algorithm [reed1954class](@cite). +Let `m` be a positive integer and `r` a nonnegative integer with `r ≤ m`. These linear codes, denoted as `RM(r, m)`, have order `r` (where `0 ≤ r ≤ m`) and codeword length `n` of `2ᵐ`. + +Two special cases of generator(RM(r, m)) exist: + 1. `generator(RM(0, m))`: This is the `0ᵗʰ`-order `RM` code, similar to the binary repetition code with length `2ᵐ`. It's characterized by a single basis vector containing all ones. + 2. `generator(RM(m, m))`: This is the `mᵗʰ`-order `RM` code. It encompasses the entire field `F(2ᵐ)`, representing all possible binary strings of length `2ᵐ`. + You might be interested in consulting [raaphorst2003reed](@cite), [abbe2020reed](@cite), and [djordjevic2021quantum](@cite) as well. -The ECC Zoo has an [entry for this family](https://errorcorrectionzoo.org/c/reed_muller) -""" +The dimension of `RM(m - r - 1, m)` equals the dimension of the dual of `RM(r, m)`. Thus, `RM(m - r - 1, m) = RM(r, m)^⊥` shows that the [dual code](https://en.wikipedia.org/wiki/Dual_code) of `RM(r, m)` is `RM(m − r − 1, m)`, indicating the parity check matrix of `RM(r, m)` is the generator matrix for `RM(m - r - 1, m)`. -abstract type ClassicalCode end +The ECC Zoo has an [entry for this family](https://errorcorrectionzoo.org/c/reed_muller). +See also: `RecursiveReedMuller` +""" struct ReedMuller <: ClassicalCode r::Int m::Int function ReedMuller(r, m) - if r < 0 || m < 1 || m >= 11 - throw(ArgumentError("Invalid parameters: r must be non-negative and m must be positive and < 11 in order to obtain a valid code and to remain tractable")) + if r < 0 || r > m + throw(ArgumentError("Invalid parameters: r must be non-negative and r ≤ m in order to valid code.")) end new(r, m) end end -function variables_xi(m, i) - return repeat([fill(1, 2^(m - i - 1)); fill(0, 2^(m - i - 1))], outer = 2^i) +function _variablesₓᵢ_rm(m, i) + return repeat([fill(1, 2 ^ (m - i - 1)); fill(0, 2 ^ (m - i - 1))], outer = 2 ^ i) end -function vmult(vecs...) +function _vmult_rm(vecs...) return [reduce(*, a, init=1) for a in zip(vecs...)] end -function parity_checks(c::ReedMuller) - r=c.r - m=c.m - xi = [variables_xi(m, i) for i in 0:m - 1] - row_matrices = [reduce(vmult, [xi[i + 1] for i in S], init = ones(Int, 2^m)) for s in 0:r for S in combinations(0:m - 1, s)] +function generator(c::ReedMuller) + r = c.r + m = c.m + xᵢ = [_variablesₓᵢ_rm(m, i) for i in 0:m - 1] + row_matrices = [reduce(_vmult_rm, [xᵢ[i + 1] for i in S], init = ones(Int, 2 ^ m)) for s in 0:r for S in combinations(0:m - 1, s)] rows = length(row_matrices) cols = length(row_matrices[1]) - H = reshape(vcat(row_matrices...), cols, rows)' -end \ No newline at end of file + G = reshape(vcat(row_matrices...), cols, rows)' + G = Matrix{Bool}(G) + return G +end + +function parity_checks(c::ReedMuller) + H = generator(ReedMuller(c.m - c.r - 1, c.m)) + return H +end + +code_n(c::ReedMuller) = 2 ^ c.m + +code_k(c::ReedMuller) = sum(binomial.(c.m, 0:c.r)) + +distance(c::ReedMuller) = 2 ^ (c.m - c.r) + +rate(c::ReedMuller) = code_k(c::ReedMuller) / code_n(c::ReedMuller) diff --git a/test/test_ecc_reedmuller.jl b/test/test_ecc_reedmuller.jl index 12031d71f..e93cc2648 100644 --- a/test/test_ecc_reedmuller.jl +++ b/test/test_ecc_reedmuller.jl @@ -1,68 +1,130 @@ @testitem "Reed-Muller" begin - using Nemo - using Combinatorics + using Test + using Nemo: echelon_form, matrix, GF using LinearAlgebra + using QuantumClifford using QuantumClifford.ECC - using QuantumClifford.ECC: AbstractECC, ReedMuller + using QuantumClifford.ECC: AbstractECC, ReedMuller, generator, RecursiveReedMuller - function binomial_coeff_sum(r, m) - total = 0 - for i in 0:r - total += length(combinations(1:m, i)) + function designed_distance(matrix, m, r) + distance = 2 ^ (m - r) + for row in eachrow(matrix) + count = sum(row) + if count < distance + return false + end end - return total + return true end - @testset "Test RM(r, m) Matrix Rank" begin - for m in 2:5 - for r in 0:m - 1 - H = parity_checks(ReedMuller(r, m)) - mat = Nemo.matrix(Nemo.GF(2), H) + # Generate binary matrix representing all possible binary strings of length `2ᵐ` encompassing the entire field `F(2ᵐ)`. + function check_RM_m_m(m::Int) + n = 2^m + matrix = Matrix{Bool}(undef, n, n) + for i in 0:(n-1) + for j in 0:(n-1) + matrix[i+1, j+1] = ((i & j) == j) + end + end + return matrix + end + + @testset "Test RM(r, m) properties" begin + for m in 3:10 + for r in 3:m-1 + H = generator(ReedMuller(r, m)) + mat = matrix(GF(2), H) computed_rank = LinearAlgebra.rank(mat) - expected_rank = binomial_coeff_sum(r, m) + expected_rank = sum(binomial.(m, 0:r)) @test computed_rank == expected_rank + @test designed_distance(H, m, r) == true + @test rate(ReedMuller(r, m)) == sum(binomial.(m, 0:r)) / 2 ^ m == rate(RecursiveReedMuller(r, m)) + @test code_n(ReedMuller(r, m)) == 2 ^ m == code_n(RecursiveReedMuller(r, m)) + @test code_k(ReedMuller(r, m)) == sum(binomial.(m, 0:r)) == code_k(RecursiveReedMuller(r, m)) + @test distance(ReedMuller(r, m)) == 2 ^ (m - r) == distance(RecursiveReedMuller(r, m)) + H₁ = generator(RecursiveReedMuller(r, m)) + # generator(RecursiveReedMuller(r, m)) is canonically equivalent to the generator(ReedMuller(r, m)) under reduced row echelon form. + @test echelon_form(matrix(GF(2), Matrix{Int64}(H))) == echelon_form(matrix(GF(2), Matrix{Int64}(H₁))) + H = parity_checks(ReedMuller(r, m)) + H₁ = parity_checks(RecursiveReedMuller(r, m)) + # parity_checks(RecursiveReedMuller(r, m)) is canonically equivalent to the parity_checks(ReedMuller(r, m)) under reduced row echelon form. + @test echelon_form(matrix(GF(2), Matrix{Int64}(H))) == echelon_form(matrix(GF(2), Matrix{Int64}(H₁))) + # dim(ReedMuller(m - r - 1, m)) = dim(ReedMuller(r, m)^⊥). + # ReedMuller(m - r - 1, m) = ReedMuller(r, m)^⊥ ∴ parity check matrix (H) of ReedMuller(r, m) is the generator matrix (G) for ReedMuller(m - r - 1, m). + H₁ = parity_checks(ReedMuller(m - r - 1, m)) + G₂ = generator(ReedMuller(r, m)) + @test size(H₁) == size(G₂) + @test echelon_form(matrix(GF(2), Matrix{Int64}(H₁))) == echelon_form(matrix(GF(2), Matrix{Int64}(G₂))) + G₃ = generator(ReedMuller(m - r - 1, m)) + H₄ = parity_checks(ReedMuller(r, m)) + @test size(G₃) == size(H₄) + # dim(RecursiveReedMuller(m - r - 1, m)) = dim(RecursiveReedMuller(r, m)^⊥). + # RecursiveReedMuller(m - r - 1, m) = RecursiveReedMuller(r, m)^⊥ ∴ parity check matrix (H) of RecursiveReedMuller(r, m) is the generator matrix (G) for RecursiveReedMuller(m - r - 1, m). + H₁ = parity_checks(RecursiveReedMuller(m - r - 1, m)) + G₂ = generator(RecursiveReedMuller(r, m)) + @test echelon_form(matrix(GF(2), Matrix{Int64}(H₄))) == echelon_form(matrix(GF(2), Matrix{Int64}(G₃))) + @test echelon_form(matrix(GF(2), Matrix{Int64}(H₁))) == echelon_form(matrix(GF(2), Matrix{Int64}(G₂))) + G₃ = generator(RecursiveReedMuller(m - r - 1, m)) + H₄ = parity_checks(RecursiveReedMuller(r, m)) + @test size(G₃) == size(H₄) + @test echelon_form(matrix(GF(2), Matrix{Int64}(H₄))) == echelon_form(matrix(GF(2), Matrix{Int64}(G₃))) end end end - @testset "Testing common examples of RM(r,m) codes [raaphorst2003reed](@cite), [djordjevic2021quantum](@cite), [abbe2020reed](@cite)" begin + @testset "Test special case 1: generator(RM(0, m))" begin + for m in 3:10 + H = generator(ReedMuller(0, m)) + expected = ones(Int64, 1, 2^m) + @test H == expected + end + end - #RM(0,3) - @test parity_checks(ReedMuller(0,3)) == [1 1 1 1 1 1 1 1] + @testset "Test special case 2: generator(RM(m, m))" begin + for m in 3:10 + H = generator(ReedMuller(m, m)) + expected = check_RM_m_m(m) + @test echelon_form(matrix(GF(2), Matrix{Int64}(H))) == echelon_form(matrix(GF(2), expected)) + end + end - #RM(1,3) - @test parity_checks(ReedMuller(1,3)) == [1 1 1 1 1 1 1 1; - 1 1 1 1 0 0 0 0; - 1 1 0 0 1 1 0 0; - 1 0 1 0 1 0 1 0] - #RM(2,3) - @test parity_checks(ReedMuller(2,3)) == [1 1 1 1 1 1 1 1; - 1 1 1 1 0 0 0 0; - 1 1 0 0 1 1 0 0; - 1 0 1 0 1 0 1 0; - 1 1 0 0 0 0 0 0; - 1 0 1 0 0 0 0 0; - 1 0 0 0 1 0 0 0] - #RM(3,3) - @test parity_checks(ReedMuller(3,3)) == [1 1 1 1 1 1 1 1; - 1 1 1 1 0 0 0 0; - 1 1 0 0 1 1 0 0; - 1 0 1 0 1 0 1 0; - 1 1 0 0 0 0 0 0; - 1 0 1 0 0 0 0 0; - 1 0 0 0 1 0 0 0; - 1 0 0 0 0 0 0 0] - #RM(2,4) - @test parity_checks(ReedMuller(2,4)) == [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1; - 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0; - 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0; - 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0; - 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0; - 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0; - 1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0; - 1 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0; - 1 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0; - 1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0; - 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0] + @testset "Test common examples of RM(r,m) codes" begin + # Examples from [raaphorst2003reed](@cite), [djordjevic2021quantum](@cite), [abbe2020reed](@cite). + # RM(0,3) + @test generator(ReedMuller(0,3)) == [1 1 1 1 1 1 1 1] + # RM(1,3) + @test generator(ReedMuller(1,3)) == [1 1 1 1 1 1 1 1; + 1 1 1 1 0 0 0 0; + 1 1 0 0 1 1 0 0; + 1 0 1 0 1 0 1 0] + # RM(2,3) + @test generator(ReedMuller(2,3)) == [1 1 1 1 1 1 1 1; + 1 1 1 1 0 0 0 0; + 1 1 0 0 1 1 0 0; + 1 0 1 0 1 0 1 0; + 1 1 0 0 0 0 0 0; + 1 0 1 0 0 0 0 0; + 1 0 0 0 1 0 0 0] + # RM(3,3) + @test generator(ReedMuller(3,3)) == [1 1 1 1 1 1 1 1; + 1 1 1 1 0 0 0 0; + 1 1 0 0 1 1 0 0; + 1 0 1 0 1 0 1 0; + 1 1 0 0 0 0 0 0; + 1 0 1 0 0 0 0 0; + 1 0 0 0 1 0 0 0; + 1 0 0 0 0 0 0 0] + # RM(2,4) + @test generator(ReedMuller(2,4)) == [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1; + 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0; + 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0; + 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0; + 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0; + 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0; + 1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0; + 1 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0; + 1 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0; + 1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0; + 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0] end end diff --git a/test/test_ecc_throws.jl b/test/test_ecc_throws.jl index bd648327b..53055f8cf 100644 --- a/test/test_ecc_throws.jl +++ b/test/test_ecc_throws.jl @@ -1,9 +1,15 @@ @testitem "ECC throws" begin - using QuantumClifford.ECC: ReedMuller, BCH + + using QuantumClifford.ECC: ReedMuller, BCH, RecursiveReedMuller @test_throws ArgumentError ReedMuller(-1, 3) @test_throws ArgumentError ReedMuller(1, 0) + @test_throws ArgumentError ReedMuller(4, 2) @test_throws ArgumentError BCH(2, 2) @test_throws ArgumentError BCH(3, 4) + + @test_throws ArgumentError RecursiveReedMuller(-1, 3) + @test_throws ArgumentError RecursiveReedMuller(1, 0) + @test_throws ArgumentError RecursiveReedMuller(4, 2) end From 3616933f23303211c8050a6a08701d47a956a053 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Sat, 14 Sep 2024 10:11:43 +0500 Subject: [PATCH 27/66] Reduces JET errors (#359) Co-authored-by: Stefan Krastanov Co-authored-by: Stefan Krastanov --- src/QuantumClifford.jl | 13 +++++++++-- src/dense_cliffords.jl | 2 +- src/ecc/ECC.jl | 5 +++-- src/ecc/decoder_pipeline.jl | 2 +- test/test_jet.jl | 43 +++++++++++++++++++++---------------- 5 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index d54ec03b6..ce87adab6 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -758,10 +758,19 @@ function comm!(v, l::PauliOperator, r::Tableau) v end comm!(v, l::Tableau, r::PauliOperator) = comm!(v, r, l) -@inline comm!(v, l::PauliOperator, r::Stabilizer, i::Int) = comm!(v, l, tab(r), i) -@inline comm!(v, l::Stabilizer, r::PauliOperator, i::Int) = comm!(v, tab(l), r, i) @inline comm!(v, l::PauliOperator, r::Stabilizer) = comm!(v, l, tab(r)) @inline comm!(v, l::Stabilizer, r::PauliOperator) = comm!(v, tab(l), r) +function comm!(v, l::PauliOperator, r::Tableau, i) + v[i] = comm(l,r,i) + v +end +comm!(v, l::Tableau, r::PauliOperator, i) = comm!(v, r, l, i) +@inline comm!(v, l::PauliOperator, r::Stabilizer, i::Int) = comm!(v, l, tab(r), i) +@inline comm!(v, l::Stabilizer, r::PauliOperator, i::Int) = comm!(v, tab(l), r, i) +function comm!(v, s::Tableau, l::Int, r::Int) + v[l] = comm(s, l, r) + v +end @inline comm!(v, s::Stabilizer, l::Int, r::Int) = comm!(v, tab(s), l, r) diff --git a/src/dense_cliffords.jl b/src/dense_cliffords.jl index 20d2cc305..baec10f6e 100644 --- a/src/dense_cliffords.jl +++ b/src/dense_cliffords.jl @@ -82,7 +82,7 @@ function row_limit(str, limit=50) end digits_subchars = collect("₀₁₂₃₄₅₆₇₈₉") -digits_substr(n,nwidth) = join(([digits_subchars[d+1] for d in reverse(digits(n, pad=nwidth))])) +digits_substr(n::Int,nwidth::Int) = join(([digits_subchars[d+1] for d in reverse(digits(n, pad=nwidth))])) function Base.copy(c::CliffordOperator) CliffordOperator(copy(c.tab)) diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 5498f2ad9..955ec5818 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -339,8 +339,9 @@ isdegenerate(c::AbstractECC, args...) = isdegenerate(parity_checks(c), args...) isdegenerate(c::AbstractStabilizer, args...) = isdegenerate(stabilizerview(c), args...) function isdegenerate(H::Stabilizer, errors) # Described in https://quantumcomputing.stackexchange.com/questions/27279 - syndromes = comm.((H,), errors) # TODO This can be optimized by having something that always returns bitvectors - return length(Set(syndromes)) != length(errors) + syndromes = map(e -> comm(H,e), errors) # TODO This can be optimized by having something that always returns bitvectors + syndrome_set = Set(syndromes) + return length(syndrome_set) != length(errors) end function isdegenerate(H::Stabilizer, d::Int=1) diff --git a/src/ecc/decoder_pipeline.jl b/src/ecc/decoder_pipeline.jl index d1fbe897f..81fd1af30 100644 --- a/src/ecc/decoder_pipeline.jl +++ b/src/ecc/decoder_pipeline.jl @@ -132,7 +132,7 @@ function evaluate_decoder(d::AbstractSyndromeDecoder, setup::AbstractECCSetup, n physical_noisy_circ, syndrome_bits, n_anc = physical_ECC_circuit(H, setup) encoding_circ = naive_encoding_circuit(H) - preX = [sHadamard(i) for i in n-k+1:n] + preX = sHadamard[sHadamard(i) for i in n-k+1:n] mdH = MixedDestabilizer(H) logX_circ, _, logX_bits = naive_syndrome_circuit(logicalxview(mdH), n_anc+1, last(syndrome_bits)+1) diff --git a/test/test_jet.jl b/test/test_jet.jl index ee8817840..304f3b78c 100644 --- a/test/test_jet.jl +++ b/test/test_jet.jl @@ -1,22 +1,27 @@ @testitem "JET checks" tags=[:jet] begin -using JET -using ArrayInterface -using Static -using Graphs -using StridedViews -using LinearAlgebra + using JET + using Test + using ArrayInterface + using Static + using Graphs + using StridedViews + using LinearAlgebra + using Nemo + using AbstractAlgebra -rep = report_package("QuantumClifford"; - ignored_modules=( - AnyFrameModule(Graphs.LinAlg), - AnyFrameModule(Graphs.SimpleGraphs), - AnyFrameModule(ArrayInterface), - AnyFrameModule(Static), - AnyFrameModule(StridedViews), - AnyFrameModule(LinearAlgebra), - ) -) -@show rep -@test_broken length(JET.get_reports(rep)) == 0 -@test length(JET.get_reports(rep)) <= 23 + rep = report_package("QuantumClifford"; + ignored_modules=( + AnyFrameModule(Graphs.LinAlg), + AnyFrameModule(Graphs.SimpleGraphs), + AnyFrameModule(ArrayInterface), + AnyFrameModule(Static), + AnyFrameModule(StridedViews), + AnyFrameModule(LinearAlgebra), + AnyFrameModule(Nemo), + AnyFrameModule(AbstractAlgebra), + )) + + @show rep + @test_broken length(JET.get_reports(rep)) == 0 + @test length(JET.get_reports(rep)) <= 11 end From c97ab944aa3e2a807da244660783625a3481b351 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Sat, 14 Sep 2024 10:27:35 +0500 Subject: [PATCH 28/66] Fixes #335: Sanity checks on SparseGate operations (#341) Co-authored-by: Stefan Krastanov --- src/misc_ops.jl | 10 ++++++++++ test/test_throws.jl | 3 +++ 2 files changed, 13 insertions(+) diff --git a/src/misc_ops.jl b/src/misc_ops.jl index 8587e28ed..02be629c7 100644 --- a/src/misc_ops.jl +++ b/src/misc_ops.jl @@ -29,11 +29,21 @@ end struct SparseGate{T<:Tableau} <: AbstractCliffordOperator # TODO simplify type parameters (remove nesting) cliff::CliffordOperator{T} indices::Vector{Int} + function SparseGate(cliff::CliffordOperator{T}, indices::Vector{Int}) where T<:Tableau + if length(indices) != nqubits(cliff) + throw(ArgumentError("The number of target qubits (indices) must match the qubit count in the CliffordOperator.")) + end + new{T}(cliff, indices) + end end SparseGate(c,t::Tuple) = SparseGate(c,collect(t)) function apply!(state::AbstractStabilizer, g::SparseGate; kwargs...) + m = maximum(g.indices) + if m > nqubits(state) + throw(ArgumentError(lazy"SparseGate was attempted on invalid qubit index $(m) when the state contains only $(nqubits(state)) qubits.")) + end apply!(state, g.cliff, g.indices; kwargs...) end diff --git a/test/test_throws.jl b/test/test_throws.jl index 2c67f4f4a..bf8ba942c 100644 --- a/test/test_throws.jl +++ b/test/test_throws.jl @@ -45,6 +45,9 @@ @test_throws ArgumentError one(typeof(T"X"), 1, basis=:U) + @test_throws ArgumentError SparseGate(random_clifford(2), [1, 2, 3]) + @test_throws ArgumentError apply!(random_stabilizer(2), SparseGate(random_clifford(3), [1, 2, 3])) + for gt in subtypes(QuantumClifford.AbstractSingleQubitOperator) gt == SingleQubitOperator && continue @test_throws ArgumentError gt(0) From 381f43cdf6771f7a17224c3b23c7db7f6dc412fa Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Sat, 14 Sep 2024 10:59:25 +0500 Subject: [PATCH 29/66] fixes #208: Constructing only valid Stabilizer(phases, xs, zs) (#350) --------- Co-authored-by: Stefan Krastanov Co-authored-by: Stefan Krastanov --- src/QuantumClifford.jl | 19 +++++--- test/test_throws.jl | 98 ++++++++++++++++++++++-------------------- 2 files changed, 65 insertions(+), 52 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index ce87adab6..9101f2108 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -149,11 +149,20 @@ function Tableau(paulis::AbstractVector{PauliOperator{Tₚ,Tᵥ}}) where {Tₚ<: tab end -Tableau(phases::AbstractVector{UInt8}, xs::AbstractMatrix{Bool}, zs::AbstractMatrix{Bool}) = Tableau( - phases, size(xs,2), - vcat(hcat((BitArray(xs[i,:]).chunks for i in 1:size(xs,1))...)::Matrix{UInt}, - hcat((BitArray(zs[i,:]).chunks for i in 1:size(zs,1))...)::Matrix{UInt}) # type assertions to help Julia infer types -) +function Tableau(phases::AbstractVector{UInt8}, xs::AbstractMatrix{Bool}, zs::AbstractMatrix{Bool}) + r_xs = size(xs, 1) + r_zs = size(zs, 1) + if length(phases) != r_xs || r_xs != r_zs + throw(DimensionMismatch(lazy"The length of phases ($(length(phases))), rows of xs ($r_xs), rows of zs ($r_zs) must all be equal.")) + end + Tableau( + phases,size(xs, 2), + vcat( + hcat((BitArray(xs[i, :]).chunks for i in 1:r_xs)...)::Matrix{UInt}, + hcat((BitArray(zs[i, :]).chunks for i in 1:r_zs)...)::Matrix{UInt} # type assertions to help Julia infer types + ) + ) +end Tableau(phases::AbstractVector{UInt8}, xzs::AbstractMatrix{Bool}) = Tableau(phases, xzs[:,1:end÷2], xzs[:,end÷2+1:end]) diff --git a/test/test_throws.jl b/test/test_throws.jl index bf8ba942c..018a0afde 100644 --- a/test/test_throws.jl +++ b/test/test_throws.jl @@ -1,69 +1,73 @@ @testitem "throws" begin - using QuantumClifford: rank, mul_left!, mul_right! - using InteractiveUtils: subtypes + using QuantumClifford: rank, mul_left!, mul_right! + using InteractiveUtils: subtypes - @test_throws DimensionMismatch CliffordOperator(T"XXX ZZ_") + @test_throws DimensionMismatch CliffordOperator(T"XXX ZZ_") - @test_throws DimensionMismatch tCNOT*S"X" + @test_throws DimensionMismatch tCNOT*S"X" - #@test_throws DomainError bigram(random_stabilizer(50), clip=false) + #@test_throws DomainError bigram(random_stabilizer(50), clip=false) - @test_throws DomainError logdot(S"XX", S"XX ZZ") - @test_throws DimensionMismatch logdot(S"X", S"XX ZZ") + @test_throws DomainError logdot(S"XX", S"XX ZZ") + @test_throws DimensionMismatch logdot(S"X", S"XX ZZ") - @test_throws BadDataStructure rank(S"X") - @test_throws BadDataStructure rank(Destabilizer(S"X")) + @test_throws BadDataStructure rank(S"X") + @test_throws BadDataStructure rank(Destabilizer(S"X")) - @test_throws DimensionMismatch mul_left!(P"X", P"XX") - @test_throws DimensionMismatch mul_right!(P"X", P"XX") + @test_throws DimensionMismatch mul_left!(P"X", P"XX") + @test_throws DimensionMismatch mul_right!(P"X", P"XX") - @test_throws ArgumentError StabMixture(S"XX") + @test_throws ArgumentError StabMixture(S"XX") - @test_throws ArgumentError UnitaryPauliChannel([P"X"], [1,2]) - @test_throws ArgumentError UnitaryPauliChannel([P"X",P"XX"], [1,2]) + @test_throws ArgumentError UnitaryPauliChannel([P"X"], [1,2]) + @test_throws ArgumentError UnitaryPauliChannel([P"X",P"XX"], [1,2]) - @test_throws ArgumentError embed(10,2,P"XX") - @test_throws ArgumentError embed(10,[2,3],P"X") + @test_throws ArgumentError embed(10,2,P"XX") + @test_throws ArgumentError embed(10,[2,3],P"X") - struct A <: QuantumClifford.AbstractOperation end - @test_throws ArgumentError applybranches(S"X",A()) + struct A <: QuantumClifford.AbstractOperation end + @test_throws ArgumentError applybranches(S"X",A()) - @test_throws BadDataStructure project!(Destabilizer(S"XX"), P"ZZ") + @test_throws BadDataStructure project!(Destabilizer(S"XX"), P"ZZ") - @test_throws DimensionMismatch reset_qubits!(ghz(4), ghz(3), [1,2]) - @test_throws DimensionMismatch reset_qubits!(ghz(3), ghz(4), [1,2,3,4]) - @test_throws DimensionMismatch reset_qubits!(MixedStabilizer(ghz(4)), MixedStabilizer(ghz(3)), [1,2]) - @test_throws DimensionMismatch reset_qubits!(MixedStabilizer(ghz(3)), MixedStabilizer(ghz(4)), [1,2,3,4]) - @test_throws DimensionMismatch reset_qubits!(MixedDestabilizer(ghz(4)), MixedDestabilizer(ghz(3)), [1,2]) - @test_throws DimensionMismatch reset_qubits!(MixedDestabilizer(ghz(3)), MixedDestabilizer(ghz(4)), [1,2,3,4]) + @test_throws DimensionMismatch reset_qubits!(ghz(4), ghz(3), [1,2]) + @test_throws DimensionMismatch reset_qubits!(ghz(3), ghz(4), [1,2,3,4]) + @test_throws DimensionMismatch reset_qubits!(MixedStabilizer(ghz(4)), MixedStabilizer(ghz(3)), [1,2]) + @test_throws DimensionMismatch reset_qubits!(MixedStabilizer(ghz(3)), MixedStabilizer(ghz(4)), [1,2,3,4]) + @test_throws DimensionMismatch reset_qubits!(MixedDestabilizer(ghz(4)), MixedDestabilizer(ghz(3)), [1,2]) + @test_throws DimensionMismatch reset_qubits!(MixedDestabilizer(ghz(3)), MixedDestabilizer(ghz(4)), [1,2,3,4]) - #TODO broken in other ways @test_throws DomainError MixedDestabilizer(Destabilizer(S"XX")) + #TODO broken in other ways @test_throws DomainError MixedDestabilizer(Destabilizer(S"XX")) - @test_throws DomainError 2*P"X" + @test_throws DomainError 2*P"X" - @test_throws DimensionMismatch P"X" * S"XX" + @test_throws DimensionMismatch P"X" * S"XX" - @test_throws ArgumentError one(typeof(T"X"), 1, basis=:U) + @test_throws ArgumentError one(typeof(T"X"), 1, basis=:U) - @test_throws ArgumentError SparseGate(random_clifford(2), [1, 2, 3]) - @test_throws ArgumentError apply!(random_stabilizer(2), SparseGate(random_clifford(3), [1, 2, 3])) + @test_throws ArgumentError SparseGate(random_clifford(2), [1, 2, 3]) + @test_throws ArgumentError apply!(random_stabilizer(2), SparseGate(random_clifford(3), [1, 2, 3])) - for gt in subtypes(QuantumClifford.AbstractSingleQubitOperator) - gt == SingleQubitOperator && continue - @test_throws ArgumentError gt(0) - @test_throws ArgumentError gt(-1) - end + for gt in subtypes(QuantumClifford.AbstractSingleQubitOperator) + gt == SingleQubitOperator && continue + @test_throws ArgumentError gt(0) + @test_throws ArgumentError gt(-1) + end - for gt in subtypes(QuantumClifford.AbstractTwoQubitOperator) - @test_throws ArgumentError gt(0,1) - @test_throws ArgumentError gt(-1,1) - @test_throws ArgumentError gt(2,2) - end + for gt in subtypes(QuantumClifford.AbstractTwoQubitOperator) + @test_throws ArgumentError gt(0,1) + @test_throws ArgumentError gt(-1,1) + @test_throws ArgumentError gt(2,2) + end + + for m in [sMX,sMZ,sMY,sMRX,sMRZ,sMRY] + @test_throws ArgumentError m(0) + @test_throws ArgumentError m(-1) + @test_throws ArgumentError m(0,1) + @test_throws ArgumentError m(-1,0) + end + + @test_throws DimensionMismatch Stabilizer(fill(0x0, 2), Matrix{Bool}([1 0 1;1 1 1; 1 0 1]), Matrix{Bool}([1 0 0;1 1 1;1 0 1])) + @test_throws DimensionMismatch Stabilizer(fill(0x0, 2), Matrix{Bool}([1 0 1 1 0 0; 1 1 1 1 1 1; 1 0 1 1 0 1])) - for m in [sMX,sMZ,sMY,sMRX,sMRZ,sMRY] - @test_throws ArgumentError m(0) - @test_throws ArgumentError m(-1) - @test_throws ArgumentError m(0,1) - @test_throws ArgumentError m(-1,0) - end end From 05491d9a142abbb2aae79ac398344ab1472895d9 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Sat, 14 Sep 2024 11:19:56 +0500 Subject: [PATCH 30/66] adding new single-qubit gates (#333) Co-authored-by: Stefan Krastanov Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 1 + src/QuantumClifford.jl | 1 + src/symbolic_cliffords.jl | 65 ++++++++++++++++++++++++++------------- test/test_symcliff.jl | 13 +++++++- 4 files changed, 58 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d46dc8c4..2ee7c838d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ## dev +- Implementing many more named single-qubit gates following naming convention similar to the stim package in python. - **(fix)** Bug fix to the `parity_checks(ReedMuller(r, m))` of classical Reed-Muller code (it was returning generator matrix). - `RecursiveReedMuller` code implementation as an alternative implementation of `ReedMuller`. diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 9101f2108..407af5959 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -48,6 +48,7 @@ export # Symbolic Clifford Ops AbstractSymbolicOperator, AbstractSingleQubitOperator, AbstractTwoQubitOperator, sHadamard, sPhase, sInvPhase, SingleQubitOperator, sId1, sX, sY, sZ, + sHadamardXY, sHadamardYZ, sSQRTX, sInvSQRTX, sSQRTY, sInvSQRTY, sCXYZ, sCZYX, sCNOT, sCPHASE, sSWAP, sXCX, sXCY, sXCZ, sYCX, sYCY, sYCZ, sZCX, sZCY, sZCZ, sZCrY, sInvZCrY, diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index 38be9eeb4..781609923 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -85,12 +85,20 @@ macro qubitop1(name, kernel) end end -@qubitop1 Hadamard (z , x , x!=0 && z!=0) -@qubitop1 Phase (x , x⊻z , x!=0 && z!=0) -@qubitop1 InvPhase (x , x⊻z , x!=0 && z==0) -@qubitop1 X (x , z , z!=0) -@qubitop1 Y (x , z , (x⊻z)!=0) -@qubitop1 Z (x , z , x!=0) +@qubitop1 Hadamard (z ,x , x!=0 && z!=0) +@qubitop1 HadamardXY (x ,x⊻z , x==0 && z!=0) +@qubitop1 HadamardYZ (x⊻z ,z , x!=0 && z==0) +@qubitop1 Phase (x ,x⊻z , x!=0 && z!=0) +@qubitop1 InvPhase (x ,x⊻z , x!=0 && z==0) +@qubitop1 X (x ,z , z!=0) +@qubitop1 Y (x ,z , (x⊻z)!=0) +@qubitop1 Z (x ,z , x!=0) +@qubitop1 SQRTX (x⊻z ,z , x==0 && z!=0) +@qubitop1 InvSQRTX (x⊻z ,z , x!=0 && z!=0) +@qubitop1 SQRTY (z ,x , z==0) +@qubitop1 InvSQRTY (z ,x , z!=0 && x==0) +@qubitop1 CXYZ (x⊻z ,x , z==0 && x==0) +@qubitop1 CZYX (z ,x⊻z , z==0 && x==0) """A "symbolic" single-qubit Identity operation. @@ -177,13 +185,21 @@ function _apply!(stab::AbstractStabilizer, op::SingleQubitOperator; phases::Val{ stab end -SingleQubitOperator(h::sHadamard) = SingleQubitOperator(h.q, false, true , true , false, false, false) -SingleQubitOperator(p::sPhase) = SingleQubitOperator(p.q, true , true , false, true , false, false) -SingleQubitOperator(p::sInvPhase) = SingleQubitOperator(p.q, true , true , false, true , true , false) -SingleQubitOperator(p::sId1) = SingleQubitOperator(p.q, true , false, false, true , false, false) -SingleQubitOperator(p::sX) = SingleQubitOperator(p.q, true , false, false, true , false, true) -SingleQubitOperator(p::sY) = SingleQubitOperator(p.q, true , false, false, true , true , true) -SingleQubitOperator(p::sZ) = SingleQubitOperator(p.q, true , false, false, true , true , false) +SingleQubitOperator(h::sHadamard) = SingleQubitOperator(h.q, false, true , true , false, false, false) +SingleQubitOperator(p::sPhase) = SingleQubitOperator(p.q, true , true , false, true , false, false) +SingleQubitOperator(p::sInvPhase) = SingleQubitOperator(p.q, true , true , false, true , true , false) +SingleQubitOperator(p::sId1) = SingleQubitOperator(p.q, true , false, false, true , false, false) +SingleQubitOperator(p::sX) = SingleQubitOperator(p.q, true , false, false, true , false, true) +SingleQubitOperator(p::sY) = SingleQubitOperator(p.q, true , false, false, true , true , true) +SingleQubitOperator(p::sZ) = SingleQubitOperator(p.q, true , false, false, true , true , false) +SingleQubitOperator(p::sCXYZ) = SingleQubitOperator(p.q, true , true , true , false, false, false) +SingleQubitOperator(p::sCZYX) = SingleQubitOperator(p.q, false, true , true , true , false, false) +SingleQubitOperator(p::sHadamardXY) = SingleQubitOperator(p.q, true , true , false, true , false, true) +SingleQubitOperator(p::sHadamardYZ) = SingleQubitOperator(p.q, true , false, true , true , true , false) +SingleQubitOperator(p::sSQRTX) = SingleQubitOperator(p.q, true , false, true , true , false, true) +SingleQubitOperator(p::sInvSQRTX) = SingleQubitOperator(p.q, true , false, true , true , false, false) +SingleQubitOperator(p::sSQRTY) = SingleQubitOperator(p.q, false, true , true , false, true , false) +SingleQubitOperator(p::sInvSQRTY) = SingleQubitOperator(p.q, false, true , true , false, false, true) SingleQubitOperator(o::SingleQubitOperator) = o function SingleQubitOperator(op::CliffordOperator, qubit) nqubits(op)==1 || throw(DimensionMismatch("You are trying to convert a multiqubit `CliffordOperator` into a symbolic `SingleQubitOperator`.")) @@ -232,14 +248,21 @@ function LinearAlgebra.inv(op::SingleQubitOperator) return SingleQubitOperator(c, op.q) end -LinearAlgebra.inv(h::sHadamard) = sHadamard(h.q) -LinearAlgebra.inv(p::sPhase) = sInvPhase(p.q) -LinearAlgebra.inv(p::sInvPhase) = sPhase(p.q) -LinearAlgebra.inv(p::sId1) = sId1(p.q) -LinearAlgebra.inv(p::sX) = sX(p.q) -LinearAlgebra.inv(p::sY) = sY(p.q) -LinearAlgebra.inv(p::sZ) = sZ(p.q) - +LinearAlgebra.inv(h::sHadamard) = sHadamard(h.q) +LinearAlgebra.inv(p::sPhase) = sInvPhase(p.q) +LinearAlgebra.inv(p::sInvPhase) = sPhase(p.q) +LinearAlgebra.inv(p::sId1) = sId1(p.q) +LinearAlgebra.inv(p::sX) = sX(p.q) +LinearAlgebra.inv(p::sY) = sY(p.q) +LinearAlgebra.inv(p::sZ) = sZ(p.q) +LinearAlgebra.inv(p::sHadamardXY) = sHadamardXY(p.q) +LinearAlgebra.inv(p::sHadamardYZ) = sHadamardYZ(p.q) +LinearAlgebra.inv(p::sSQRTX) = sInvSQRTX(p.q) +LinearAlgebra.inv(p::sInvSQRTX) = sSQRTX(p.q) +LinearAlgebra.inv(p::sSQRTY) = sInvSQRTY(p.q) +LinearAlgebra.inv(p::sInvSQRTY) = sSQRTY(p.q) +LinearAlgebra.inv(p::sCZYX) = sCXYZ(p.q) +LinearAlgebra.inv(p::sCXYZ) = sCZYX(p.q) ############################## # Two-qubit gates ############################## diff --git a/test/test_symcliff.jl b/test/test_symcliff.jl index 753d82841..1d9f2dbe1 100644 --- a/test/test_symcliff.jl +++ b/test/test_symcliff.jl @@ -66,7 +66,7 @@ end @testset "SingleQubitOperator inv methods" begin - for gate_type in [sHadamard, sX, sY, sZ, sId1 , sPhase, sInvPhase] + for gate_type in filter(gate_type -> gate_type != SingleQubitOperator, subtypes(AbstractSingleQubitOperator)) n = rand(1:10) @test CliffordOperator(inv(SingleQubitOperator(gate_type(n))), n) == inv(CliffordOperator(gate_type(n), n)) @test CliffordOperator(inv(gate_type(n)), n) == inv(CliffordOperator(gate_type(n), n)) @@ -76,6 +76,17 @@ @test CliffordOperator(inv(random_op), i) == inv(CliffordOperator(random_op, i)) @test CliffordOperator(inv(SingleQubitOperator(random_op)), i) == inv(CliffordOperator(random_op, i)) end + + @testset "Consistency checks with Stim" begin + # see https://github.com/quantumlib/Stim/blob/main/doc/gates.md + @test CliffordOperator(sCXYZ) == C"Y X" + @test CliffordOperator(sCZYX) == C"Z Y" + @test CliffordOperator(sSQRTX) == C"X -Y" + @test CliffordOperator(sSQRTY) == C"-Z X" + @test CliffordOperator(sInvSQRTX) == C"X Y" + @test CliffordOperator(sInvSQRTY) == C"Z -X" + @test CliffordOperator(sHadamardXY) == C"Y -Z" + @test CliffordOperator(sHadamardYZ) == C"-X Y" end @testset "TwoQubitOperator inv methods" begin From 4801b11a498b46241e05b6f5f32ad1ff67d93f5f Mon Sep 17 00:00:00 2001 From: Rabqubit Date: Fri, 27 Sep 2024 05:24:48 +0800 Subject: [PATCH 31/66] Lifted and lifted product codes via Hecke's GroupAlgebra (#356) --------- Co-authored-by: Stefan Krastanov Co-authored-by: hanakl --- CHANGELOG.md | 4 + Project.toml | 5 +- docs/Project.toml | 1 + docs/make.jl | 8 +- docs/src/ECC_API.md | 7 + docs/src/references.bib | 52 +++++ .../QuantumCliffordHeckeExt.jl | 19 ++ ext/QuantumCliffordHeckeExt/lifted.jl | 101 ++++++++++ ext/QuantumCliffordHeckeExt/lifted_product.jl | 187 ++++++++++++++++++ ext/QuantumCliffordHeckeExt/types.jl | 36 ++++ src/QuantumClifford.jl | 10 +- src/ecc/ECC.jl | 30 ++- src/ecc/codes/classical/lifted.jl | 10 + src/ecc/codes/lifted_product.jl | 19 ++ src/ecc/decoder_pipeline.jl | 8 +- test/Project.toml | 1 + test/test_ecc.jl | 4 +- test/test_ecc_base.jl | 45 ++++- test/test_ecc_codeproperties.jl | 5 +- test/test_ecc_decoder_all_setups.jl | 27 +++ test/test_ecc_encoding.jl | 2 +- test/test_ecc_syndromes.jl | 6 +- test/test_jet.jl | 2 + 23 files changed, 564 insertions(+), 25 deletions(-) create mode 100644 ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl create mode 100644 ext/QuantumCliffordHeckeExt/lifted.jl create mode 100644 ext/QuantumCliffordHeckeExt/lifted_product.jl create mode 100644 ext/QuantumCliffordHeckeExt/types.jl create mode 100644 src/ecc/codes/classical/lifted.jl create mode 100644 src/ecc/codes/lifted_product.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ee7c838d..762c48595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ - **(fix)** Bug fix to the `parity_checks(ReedMuller(r, m))` of classical Reed-Muller code (it was returning generator matrix). - `RecursiveReedMuller` code implementation as an alternative implementation of `ReedMuller`. +## v0.9.10 - 2024-09-26 + +- **(fix)** `ECC.code_s` now gives the number of parity checks with redundancy. If you want the number of linearly independent parity checks, you can use `LinearAlgebra.rank`. + ## v0.9.9 - 2024-08-05 - `inv` is implemented for all Clifford operator types (symbolic, dense, sparse). diff --git a/Project.toml b/Project.toml index a5dc91bfb..802c43685 100644 --- a/Project.toml +++ b/Project.toml @@ -24,6 +24,7 @@ SumTypes = "8e1ec7a9-0e02-4297-b0fe-6433085c89f2" [weakdeps] CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +Hecke = "3e1990a7-5d81-5526-99ce-9ba3ff248f21" LDPCDecoders = "3c486d74-64b9-4c60-8b1a-13a564e77efb" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" @@ -33,6 +34,7 @@ QuantumOpticsBase = "4f57444f-1401-5e15-980d-4471b28d5678" [extensions] QuantumCliffordGPUExt = "CUDA" +QuantumCliffordHeckeExt = "Hecke" QuantumCliffordLDPCDecodersExt = "LDPCDecoders" QuantumCliffordMakieExt = "Makie" QuantumCliffordPlotsExt = "Plots" @@ -46,6 +48,7 @@ Combinatorics = "1.0" DataStructures = "0.18" DocStringExtensions = "0.9" Graphs = "1.9" +Hecke = "0.28, 0.29, 0.30, 0.31, 0.32, 0.33" HostCPUFeatures = "0.1.6" ILog2 = "0.2.3" InteractiveUtils = "1.9" @@ -53,7 +56,7 @@ LDPCDecoders = "0.3.1" LinearAlgebra = "1.9" MacroTools = "0.5.9" Makie = "0.20, 0.21" -Nemo = "0.42, 0.43, 0.44, 0.45, 0.46" +Nemo = "^0.42.1, 0.43, 0.44, 0.45, 0.46" Plots = "1.38.0" PrecompileTools = "1.2" PyQDecoders = "0.2.1" diff --git a/docs/Project.toml b/docs/Project.toml index b414301a3..8ef02232a 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -5,6 +5,7 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" GraphMakie = "1ecd5474-83a3-4783-bb4f-06765db800d2" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +Hecke = "3e1990a7-5d81-5526-99ce-9ba3ff248f21" LDPCDecoders = "3c486d74-64b9-4c60-8b1a-13a564e77efb" Nemo = "2edaba10-b0f1-5616-af89-8c11ac63239a" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" diff --git a/docs/make.jl b/docs/make.jl index 699924cda..b8d13fc30 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -7,6 +7,11 @@ using QuantumClifford using QuantumClifford.Experimental.NoisyCircuits using QuantumInterface +ENV["HECKE_PRINT_BANNER"] = "false" +import Hecke + +const QuantumCliffordHeckeExt = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) + #DocMeta.setdocmeta!(QuantumClifford, :DocTestSetup, :(using QuantumClifford); recursive=true) ENV["LINES"] = 80 # for forcing `displaysize(io)` to be big enough @@ -20,8 +25,9 @@ doctest = false, clean = true, sitename = "QuantumClifford.jl", format = Documenter.HTML(size_threshold_ignore = ["API.md"]), -modules = [QuantumClifford, QuantumClifford.Experimental.NoisyCircuits, QuantumClifford.ECC, QuantumInterface], +modules = [QuantumClifford, QuantumClifford.Experimental.NoisyCircuits, QuantumClifford.ECC, QuantumInterface, QuantumCliffordHeckeExt], warnonly = [:missing_docs], +linkcheck = true, authors = "Stefan Krastanov", pages = [ "QuantumClifford.jl" => "index.md", diff --git a/docs/src/ECC_API.md b/docs/src/ECC_API.md index d77b26097..c400a2bf4 100644 --- a/docs/src/ECC_API.md +++ b/docs/src/ECC_API.md @@ -4,3 +4,10 @@ Modules = [QuantumClifford.ECC] Private = false ``` + +## Implemented in an extension requiring `Hecke.jl` + +```@autodocs +Modules = [QuantumCliffordHeckeExt] +Private = true +``` \ No newline at end of file diff --git a/docs/src/references.bib b/docs/src/references.bib index 6a50a9194..1cbde2910 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -402,3 +402,55 @@ @inproceedings{brown2013short pages = {346--350}, doi = {10.1109/ISIT.2013.6620245} } + +@article{panteleev2021degenerate, + title = {Degenerate {{Quantum LDPC Codes With Good Finite Length Performance}}}, + author = {Panteleev, Pavel and Kalachev, Gleb}, + year = {2021}, + month = nov, + journal = {Quantum}, + volume = {5}, + eprint = {1904.02703}, + primaryclass = {quant-ph}, + pages = {585}, + issn = {2521-327X}, + doi = {10.22331/q-2021-11-22-585}, + archiveprefix = {arXiv} +} + + +@inproceedings{panteleev2022asymptotically, + title = {Asymptotically Good {{Quantum}} and Locally Testable Classical {{LDPC}} Codes}, + booktitle = {Proceedings of the 54th {{Annual ACM SIGACT Symposium}} on {{Theory}} of {{Computing}}}, + author = {Panteleev, Pavel and Kalachev, Gleb}, + year = {2022}, + month = jun, + pages = {375--388}, + publisher = {ACM}, + address = {Rome Italy}, + doi = {10.1145/3519935.3520017}, + isbn = {978-1-4503-9264-8} +} + +@article{roffe2023bias, + title = {Bias-Tailored Quantum {{LDPC}} Codes}, + author = {Roffe, Joschka and Cohen, Lawrence Z. and Quintavalle, Armanda O. and Chandra, Daryus and Campbell, Earl T.}, + year = {2023}, + month = may, + journal = {Quantum}, + volume = {7}, + pages = {1005}, + doi = {10.22331/q-2023-05-15-1005} +} + +@article{raveendran2022finite, + title = {Finite {{Rate QLDPC-GKP Coding Scheme}} That {{Surpasses}} the {{CSS Hamming Bound}}}, + author = {Raveendran, Nithin and Rengaswamy, Narayanan and Rozp{\k e}dek, Filip and Raina, Ankur and Jiang, Liang and Vasi{\'c}, Bane}, + year = {2022}, + month = jul, + journal = {Quantum}, + volume = {6}, + pages = {767}, + issn = {2521-327X}, + doi = {10.22331/q-2022-07-20-767}, +} diff --git a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl new file mode 100644 index 000000000..354de8a69 --- /dev/null +++ b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl @@ -0,0 +1,19 @@ +module QuantumCliffordHeckeExt + +using DocStringExtensions + +import QuantumClifford, LinearAlgebra +import Hecke: Group, GroupElem, AdditiveGroup, AdditiveGroupElem, + GroupAlgebra, GroupAlgebraElem, FqFieldElem, representation_matrix, dim, base_ring, + multiplication_table, coefficients, abelian_group, group_algebra +import Nemo: characteristic, matrix_repr, GF, ZZ + +import QuantumClifford.ECC: AbstractECC, CSS, ClassicalCode, + hgp, code_k, code_n, code_s, iscss, parity_checks, parity_checks_x, parity_checks_z, parity_checks_xz, + two_block_group_algebra_codes, generalized_bicycle_codes, bicycle_codes + +include("types.jl") +include("lifted.jl") +include("lifted_product.jl") + +end # module diff --git a/ext/QuantumCliffordHeckeExt/lifted.jl b/ext/QuantumCliffordHeckeExt/lifted.jl new file mode 100644 index 000000000..6601ce236 --- /dev/null +++ b/ext/QuantumCliffordHeckeExt/lifted.jl @@ -0,0 +1,101 @@ +""" +$TYPEDEF + +Classical codes lifted over a group algebra, used for lifted product code construction ([panteleev2021degenerate](@cite), [panteleev2022asymptotically](@cite)) + +The parity-check matrix is constructed by applying `repr` to each element of `A`, +which is mathematically a linear map from a group algebra element to a binary matrix. +The size of the parity check matrix will enlarged with each element of `A` being inflated into a matrix. +The procedure is called a lift [panteleev2022asymptotically](@cite). + +## Constructors + +A lifted code can be constructed via the following approaches: + +1. A matrix of group algebra elements. + +2. A matrix of group elements, where a group element will be considered as a group algebra element by assigning a unit coefficient. + +3. A matrix of integers, where each integer represent the shift of a cyclic permutation. The order of the cyclic permutation should be specified. + +The default `GA` is the group algebra of `A[1, 1]`, the default representation `repr` is the permutation representation. + +## The representation function `repr` + +In this struct, we use the default representation function `default_repr` to convert a `GF(2)`-group algebra element to a binary matrix. +The default representation, provided by `Hecke`, is the permutation representation. + +We also accept a custom representation function. +Such a customization would be useful to reduce the number of bits required by the code construction. + +For example, if we use a D4 group for lifting, our default representation will be `8×8` permutation matrices, +where 8 is the group's order. +However, we can find a `4×4` matrix representation for the group, +e.g. by using the typical [`2×2` representation](https://en.wikipedia.org/wiki/Dihedral_group) +and converting it into binary representation by replacing "1" with the Pauli I, and "-1" with the Pauli X matrix. + +See also: [`LPCode`](@ref). + +$TYPEDFIELDS +""" +struct LiftedCode <: ClassicalCode + """the base matrix of the code, whose elements are in a group algebra.""" + A::GroupAlgebraElemMatrix + """the group algebra for which elements in `A` are from.""" + GA::GroupAlgebra + """ + a function that converts a group algebra element to a binary matrix; + default to be the permutation representation for GF(2)-algebra.""" + repr::Function + + function LiftedCode(A::GroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1, 1]), repr::Function) + all(elem.parent == GA for elem in A) || error("The base ring of all elements in the code must be the same as the group algebra") + new(A, GA, repr) + end +end + +default_repr(y::GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}) = Matrix((x -> Bool(Int(lift(ZZ, x)))).(representation_matrix(y))) + +""" +`LiftedCode` constructor using the default `GF(2)` representation (coefficients converted to a permutation matrix by `representation_matrix` provided by Hecke). +""" # TODO doctest example +function LiftedCode(A::Matrix{GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}}; GA::GroupAlgebra=parent(A[1,1])) + !(characteristic(base_ring(A[1, 1])) == 2) && error("The default permutation representation applies only to GF(2) group algebra; otherwise, a custom representation function should be provided") + LiftedCode(A; GA=GA, repr=default_repr) +end + +# TODO document and doctest example +function LiftedCode(group_elem_array::Matrix{<: GroupOrAdditiveGroupElem}; GA::GroupAlgebra=group_algebra(GF(2), parent(group_elem_array[1,1])), repr::Union{Function, Nothing}=nothing) + A = zeros(GA, size(group_elem_array)...) + for i in axes(group_elem_array, 1), j in axes(group_elem_array, 2) + A[i, j] = GA[A[i, j]] + end + if repr === nothing + return LiftedCode(A; GA=GA, repr=default_repr) + else + return LiftedCode(A; GA=GA, repr=repr) + end +end + +# TODO document and doctest example +function LiftedCode(shift_array::Matrix{Int}, l::Int; GA::GroupAlgebra=group_algebra(GF(2), abelian_group(l))) + A = zeros(GA, size(shift_array)...) + for i in 1:size(shift_array, 1) + for j in 1:size(shift_array, 2) + A[i, j] = GA[shift_array[i, j]%l+1] + end + end + return LiftedCode(A; GA=GA, repr=default_repr) +end + +function lift(repr::Function, mat::GroupAlgebraElemMatrix) + vcat([hcat([repr(mat[i, j]) for j in axes(mat, 2)]...) for i in axes(mat, 1)]...) +end + +function parity_checks(c::LiftedCode) + return lift(c.repr, c.A) +end + +code_n(c::LiftedCode) = size(c.A, 2) * size(zero(c.GA), 2) + +code_s(c::LiftedCode) = size(c.A, 1) * size(zero(c.GA), 1) diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl new file mode 100644 index 000000000..aecad36b3 --- /dev/null +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -0,0 +1,187 @@ +""" +$TYPEDEF + +Lifted product codes ([panteleev2021degenerate](@cite), [panteleev2022asymptotically](@cite)) + +A lifted product code is defined by the hypergraph product of a base matrices `A` and the conjugate of another base matrix `B'`. +Here, the hypergraph product is taken over a group algebra, of which the base matrices are consisting. + +The binary parity check matrix is obtained by applying `repr` to each element of the matrix resulted from the hypergraph product, which is mathematically a linear map from each group algebra element to a binary matrix. + +## Constructors + +Multiple constructors are available: + +1. Two base matrices of group algebra elements. + +2. Two lifted codes, whose base matrices are for quantum code construction. + +3. Two base matrices of group elements, where each group element will be considered as a group algebra element by assigning a unit coefficient. + +4. Two base matrices of integers, where each integer represent the shift of a cyclic permutation. The order of the cyclic permutation should be specified. + +## Examples + +A [[882, 24, d ≤ 24]] code from Appendix B of [roffe2023bias](@cite). +We use the 1st constructor to generate the code and check its length and dimension. +During the construction, we do arithmetic operations to get the group algebra elements in base matrices `A` and `B`. +Here `x` is the generator of the group algebra, i.e., offset-1 cyclic permutation, and `GA(1)` is the unit element. + +```jldoctest +julia> import Hecke: group_algebra, GF, abelian_group, gens; import LinearAlgebra: diagind; + +julia> l = 63; GA = group_algebra(GF(2), abelian_group(l)); x = gens(GA)[]; + +julia> A = zeros(GA, 7, 7); + +julia> A[diagind(A)] .= x^27; + +julia> A[diagind(A, -1)] .= x^54; + +julia> A[diagind(A, 6)] .= x^54; + +julia> A[diagind(A, -2)] .= GA(1); + +julia> A[diagind(A, 5)] .= GA(1); + +julia> B = reshape([1 + x + x^6], (1, 1)); + +julia> c1 = LPCode(A, B); + +julia> code_n(c1), code_k(c1) +(882, 24) +``` + +A [[175, 19, d ≤ 0]] code from Eq. (18) in Appendix A of [raveendran2022finite](@cite), +following the 4th constructor. + +```jldoctest +julia> base_matrix = [0 0 0 0; 0 1 2 5; 0 6 3 1]; l = 7; + +julia> c2 = LPCode(base_matrix, l .- base_matrix', l); + +julia> code_n(c2), code_k(c2) +(175, 19) +``` + +## Code subfamilies and convenience constructors for them + +- When the base matrices of the `LPCode` are 1×1, the code is called a two-block group-algebra code [`two_block_group_algebra_codes`](@ref). +- When the base matrices of the `LPCode` are 1×1 and their elements are sums of cyclic permutations, the code is called a generalized bicycle code [`generalized_bicycle_codes`](@ref). +- When the two matrices are adjoint to each other, the code is called a bicycle code [`bicycle_codes`](@ref). + +## The representation function + +In this struct, we use the default representation function `default_repr` to convert a `GF(2)`-group algebra element to a binary matrix. +The default representation, provided by `Hecke`, is the permutation representation. + +We also accept a custom representation function as detailed in [`LiftedCode`](@ref). + +See also: [`LiftedCode`](@ref), [`two_block_group_algebra_codes`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref). + +$TYPEDFIELDS +""" +struct LPCode <: AbstractECC + """the first base matrix of the code, whose elements are in a group algebra.""" + A::GroupAlgebraElemMatrix + """the second base matrix of the code, whose elements are in the same group algebra as `A`.""" + B::GroupAlgebraElemMatrix + """the group algebra for which elements in `A` and `B` are from.""" + GA::GroupAlgebra + """ + a function that converts a group algebra element to a binary matrix; + default to be the permutation representation for GF(2)-algebra.""" + repr::Function + + function LPCode(A::GroupAlgebraElemMatrix, B::GroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1]), repr::Function) + all(elem.parent == GA for elem in A) && all(elem.parent == GA for elem in B) || error("The base rings of all elements in both matrices must be the same as the group algebra") + new(A, B, GA, repr) + end + + function LPCode(c₁::LiftedCode, c₂::LiftedCode; GA::GroupAlgebra=c₁.GA, repr::Function=c₁.repr) + # we are using the group algebra and the representation function of the first lifted code + c₁.GA == GA && c₂.GA == GA || error("The base rings of both lifted codes must be the same as the group algebra") + new(c₁.A, c₂.A, GA, repr) + end +end + +# TODO document and doctest example +function LPCode(A::FqFieldGroupAlgebraElemMatrix, B::FqFieldGroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1])) + LPCode(LiftedCode(A; GA=GA, repr=default_repr), LiftedCode(B; GA=GA, repr=default_repr); GA=GA, repr=default_repr) +end + +# TODO document and doctest example +function LPCode(group_elem_array1::Matrix{<: GroupOrAdditiveGroupElem}, group_elem_array2::Matrix{<: GroupOrAdditiveGroupElem}; GA::GroupAlgebra=group_algebra(GF(2), parent(group_elem_array1[1,1]))) + LPCode(LiftedCode(group_elem_array1; GA=GA), LiftedCode(group_elem_array2; GA=GA); GA=GA, repr=default_repr) +end + +# TODO document and doctest example +function LPCode(shift_array1::Matrix{Int}, shift_array2::Matrix{Int}, l::Int; GA::GroupAlgebra=group_algebra(GF(2), abelian_group(l))) + LPCode(LiftedCode(shift_array1, l; GA=GA), LiftedCode(shift_array2, l; GA=GA); GA=GA, repr=default_repr) +end + +iscss(::Type{LPCode}) = true + +function parity_checks_xz(c::LPCode) + hx, hz = hgp(c.A, c.B') + hx, hz = lift(c.repr, hx), lift(c.repr, hz) + return hx, hz +end + +parity_checks_x(c::LPCode) = parity_checks_xz(c)[1] + +parity_checks_z(c::LPCode) = parity_checks_xz(c)[2] + +parity_checks(c::LPCode) = parity_checks(CSS(parity_checks_xz(c)...)) + +code_n(c::LPCode) = size(c.repr(zero(c.GA)), 2) * (size(c.A, 2) * size(c.B, 1) + size(c.A, 1) * size(c.B, 2)) + +code_s(c::LPCode) = size(c.repr(zero(c.GA)), 1) * (size(c.A, 1) * size(c.B, 1) + size(c.A, 2) * size(c.B, 2)) + +""" +Two-block group algebra (2GBA) codes, which are a special case of lifted product codes +from two group algebra elements `a` and `b`, used as `1x1` base matrices. + +See also: [`LPCode`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref) +""" # TODO doctest example +function two_block_group_algebra_codes(a::GroupAlgebraElem, b::GroupAlgebraElem) + A = reshape([a], (1, 1)) + B = reshape([b], (1, 1)) + LPCode(A, B) +end + +""" +Generalized bicycle codes, which are a special case of 2GBA codes (and therefore of lifted product codes). +Here the group is chosen as the cyclic group of order `l`, +and the base matrices `a` and `b` are the sum of the group algebra elements corresponding to the shifts `a_shifts` and `b_shifts`. + +See also: [`two_block_group_algebra_codes`](@ref), [`bicycle_codes`](@ref). + +A [[254, 28, 14 ≤ d ≤ 20]] code from (A1) in Appendix B of [panteleev2021degenerate](@cite). + +```jldoctest +julia> c = generalized_bicycle_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], 127); + +julia> code_n(c), code_k(c) +(254, 28) +``` +""" # TODO doctest example +function generalized_bicycle_codes(a_shifts::Array{Int}, b_shifts::Array{Int}, l::Int) + GA = group_algebra(GF(2), abelian_group(l)) + a = sum(GA[n%l+1] for n in a_shifts) + b = sum(GA[n%l+1] for n in b_shifts) + two_block_group_algebra_codes(a, b) +end + +""" +Bicycle codes are a special case of generalized bicycle codes, +where `a` and `b` are conjugate to each other. +The order of the cyclic group is `l`, and the shifts `a_shifts` and `b_shifts` are reverse to each other. + +See also: [`two_block_group_algebra_codes`](@ref), [`generalized_bicycle_codes`](@ref). +""" # TODO doctest example +function bicycle_codes(a_shifts::Array{Int}, l::Int) + GA = group_algebra(GF(2), abelian_group(l)) + a = sum(GA[n÷l+1] for n in a_shifts) + two_block_group_algebra_codes(a, a') +end diff --git a/ext/QuantumCliffordHeckeExt/types.jl b/ext/QuantumCliffordHeckeExt/types.jl new file mode 100644 index 000000000..52ccb70dd --- /dev/null +++ b/ext/QuantumCliffordHeckeExt/types.jl @@ -0,0 +1,36 @@ +const GroupOrAdditiveGroupElem = Union{GroupElem,AdditiveGroupElem} + +const GroupAlgebraElemMatrix = Union{ + Matrix{<:GroupAlgebraElem}, + LinearAlgebra.Adjoint{<:GroupAlgebraElem,<:Matrix{<:GroupAlgebraElem}} +} + +const FqFieldGroupAlgebraElemMatrix = Union{ + Matrix{<:GroupAlgebraElem{FqFieldElem,<:GroupAlgebra}}, + LinearAlgebra.Adjoint{<:GroupAlgebraElem{FqFieldElem,<:GroupAlgebra},<:Matrix{<:GroupAlgebraElem{FqFieldElem,<:GroupAlgebra}}} +} + +""" +Compute the adjoint of a group algebra element. +The adjoint is defined as the conjugate of the element in the group algebra, +i.e. the inverse of the element in the associated group. +""" +function _adjoint(a::GroupAlgebraElem{T}) where T # TODO Is this used? Should it be deleted? + A = parent(a) + d = dim(A) + v = Vector{T}(undef, d) + for i in 1:d + v[i] = zero(base_ring(A)) + end + id_index = findfirst(x -> x == 1, one(A).coeffs) + # t = zero(base_ring(A)) + mt = multiplication_table(A, copy = false) + acoeff = coefficients(a, copy = false) + for i in 1:d + if acoeff[i] != 0 + k = findfirst(x -> x==id_index, mt[i, :]) # find the inverse of i-th element in the group + v[k] += acoeff[i] + end + end + return A(v) +end diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 407af5959..ffe679a0d 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -1101,8 +1101,14 @@ end """ Check basic consistency requirements of a stabilizer. Used in tests. """ -function stab_looks_good(s) - c = tab(canonicalize!(copy(s))) +function stab_looks_good(s; remove_redundant_rows=false) + # first remove redundant rows + c = if remove_redundant_rows + s1, _, rank = canonicalize!(copy(s), ranks=true) + tab(s1[1:rank]) + else + tab(canonicalize!(copy(s))) + end nrows, ncols = size(c) all((c.phases .== 0x0) .| (c.phases .== 0x2)) || return false H = stab_to_gf2(c) diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index 955ec5818..d8e863136 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -17,10 +17,11 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, code_n, code_s, code_k, rate, distance, isdegenerate, faults_matrix, naive_syndrome_circuit, shor_syndrome_circuit, naive_encoding_circuit, - RepCode, + RepCode, LiftedCode, CSS, Shor9, Steane7, Cleve8, Perfect5, Bitflip3, Toric, Gottesman, Surface, Concat, CircuitCode, + LPCode, two_block_group_algebra_codes, generalized_bicycle_codes, bicycle_codes, random_brickwork_circuit_code, random_all_to_all_circuit_code, evaluate_decoder, CommutationCheckECCSetup, NaiveSyndromeECCSetup, ShorSyndromeECCSetup, @@ -87,14 +88,22 @@ code_n(c::AbstractECC) = code_n(parity_checks(c)) code_n(s::Stabilizer) = nqubits(s) -"""The number of stabilizer checks in a code.""" +"""The number of stabilizer checks in a code. They might not be all linearly independent, thus `code_s >= code_n-code_k`. For the number of linearly independent checks you can use `LinearAlgebra.rank`.""" function code_s end - -code_s(c::AbstractECC) = code_s(parity_checks(c)) code_s(s::Stabilizer) = length(s) +code_s(c::AbstractECC) = code_s(parity_checks(c)) + +""" +The number of logical qubits in a code. + +Note that when redundant rows exist in the parity check matrix, the number of logical qubits `code_k(c)` will be greater than `code_n(c) - code_s(c)`, where the difference equals the redundancy. +""" +function code_k(s::Stabilizer) + _, _, r = canonicalize!(Base.copy(s), ranks=true) + return code_n(s) - r +end -"""The number of logical qubits in a code.""" -code_k(c) = code_n(c) - code_s(c) +code_k(c::AbstractECC) = code_k(parity_checks(c)) """The rate of a code.""" function rate(c) @@ -354,6 +363,7 @@ include("circuits.jl") include("decoder_pipeline.jl") include("codes/util.jl") + include("codes/classical_codes.jl") include("codes/css.jl") include("codes/bitflipcode.jl") @@ -366,7 +376,13 @@ include("codes/gottesman.jl") include("codes/surface.jl") include("codes/concat.jl") include("codes/random_circuit.jl") + include("codes/classical/reedmuller.jl") -include("codes/classical/bch.jl") include("codes/classical/recursivereedmuller.jl") +include("codes/classical/bch.jl") + +# qLDPC +include("codes/classical/lifted.jl") +include("codes/lifted_product.jl") + end #module diff --git a/src/ecc/codes/classical/lifted.jl b/src/ecc/codes/classical/lifted.jl new file mode 100644 index 000000000..a7c20fa04 --- /dev/null +++ b/src/ecc/codes/classical/lifted.jl @@ -0,0 +1,10 @@ +"""Classical codes lifted over a group algebra, used for lifted product code construction ([panteleev2021degenerate](@cite), [panteleev2022asymptotically](@cite)) + +Implemented as a package extension with Hecke. Check the [QuantumClifford documentation](http://qc.quantumsavory.org/stable/ECC_API/) for more details on that extension.""" +function LiftedCode(args...; kwargs...) + ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) + if isnothing(ext) + throw("The `LiftedCode` depends on the package `Hecke` but you have not installed or imported it yet. Immediately after you import `Hecke`, the `LiftedCode` will be available.") + end + return ext.LiftedCode(args...; kwargs...) +end diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl new file mode 100644 index 000000000..338880702 --- /dev/null +++ b/src/ecc/codes/lifted_product.jl @@ -0,0 +1,19 @@ +"""Lifted product codes ([panteleev2021degenerate](@cite), [panteleev2022asymptotically](@cite)) + +Implemented as a package extension with Hecke. Check the [QuantumClifford documentation](http://qc.quantumsavory.org/stable/ECC_API/) for more details on that extension.""" +function LPCode(args...; kwargs...) + ext = Base.get_extension(QuantumClifford, :QuantumCliffordHeckeExt) + if isnothing(ext) + throw("The `LPCode` depends on the package `Hecke` but you have not installed or imported it yet. Immediately after you import `Hecke`, the `LPCode` will be available.") + end + return ext.LPCode(args...; kwargs...) +end + +"""Implemented in a package extension with Hecke.""" +function two_block_group_algebra_codes end + +"""Implemented in a package extension with Hecke.""" +function generalized_bicycle_codes end + +"""Implemented in a package extension with Hecke.""" +function bicycle_codes end diff --git a/src/ecc/decoder_pipeline.jl b/src/ecc/decoder_pipeline.jl index 81fd1af30..0488dd28a 100644 --- a/src/ecc/decoder_pipeline.jl +++ b/src/ecc/decoder_pipeline.jl @@ -101,7 +101,7 @@ function physical_ECC_circuit(H, setup::NaiveSyndromeECCSetup) end mem_error_circ = [PauliError(i, setup.mem_noise) for i in 1:nqubits(H)] - circ = [mem_error_circ..., noisy_syndrome_circ...] + circ = vcat(mem_error_circ, noisy_syndrome_circ) return circ, syndrome_bits, n_anc end @@ -119,7 +119,7 @@ function physical_ECC_circuit(H, setup::ShorSyndromeECCSetup) mem_error_circ = [PauliError(i, setup.mem_noise) for i in 1:nqubits(H)] - circ = [prep_anc..., mem_error_circ..., noisy_syndrome_circ...] + circ = vcat(prep_anc, mem_error_circ, noisy_syndrome_circ) circ, syndrome_bits, n_anc end @@ -141,12 +141,12 @@ function evaluate_decoder(d::AbstractSyndromeDecoder, setup::AbstractECCSetup, n # Evaluate the probability for X logical error (the Z-observable part of the faults matrix is used) X_error = evaluate_decoder( d, nsamples, - [encoding_circ..., physical_noisy_circ..., logZ_circ...], + vcat(encoding_circ, physical_noisy_circ, logZ_circ), syndrome_bits, logZ_bits, O[end÷2+1:end,:]) # Evaluate the probability for Z logical error (the X-observable part of the faults matrix is used) Z_error = evaluate_decoder( d, nsamples, - [preX..., encoding_circ..., physical_noisy_circ..., logX_circ...], + vcat(preX, encoding_circ, physical_noisy_circ, logX_circ), syndrome_bits, logX_bits, O[1:end÷2,:]) return (X_error, Z_error) end diff --git a/test/Project.toml b/test/Project.toml index 67977b460..cd15ae310 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -7,6 +7,7 @@ Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +Hecke = "3e1990a7-5d81-5526-99ce-9ba3ff248f21" HostCPUFeatures = "3e5b6fbb-0976-4d2c-9146-d79de83f2fb0" ILog2 = "2cd5bd5f-40a1-5050-9e10-fc8cdb6109f5" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" diff --git a/test/test_ecc.jl b/test/test_ecc.jl index 64531986d..075d27ac5 100644 --- a/test/test_ecc.jl +++ b/test/test_ecc.jl @@ -9,7 +9,7 @@ function test_naive_syndrome(c::AbstractECC, e::Bool) # create a random logical state unencoded_qubits = random_stabilizer(code_k(c)) - bufferqubits = one(Stabilizer,code_s(c)) + bufferqubits = one(Stabilizer, code_n(c) - code_k(c)) logicalqubits = bufferqubits⊗unencoded_qubits mctrajectory!(logicalqubits, naive_encoding_circuit(c)) if e @@ -50,7 +50,7 @@ ancqubits = code_s(code) regbits = ancqubits frames = PauliFrame(nframes, dataqubits+ancqubits, regbits) - circuit = [ecirc..., scirc...] + circuit = vcat(ecirc, scirc) pftrajectories(frames, circuit) @test sum(pfmeasurements(frames)) == 0 end diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index 9b4dac227..d9afc09bb 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -3,6 +3,10 @@ using QuantumClifford using QuantumClifford.ECC using InteractiveUtils +import Nemo: GF +import LinearAlgebra +import Hecke: group_algebra, abelian_group, gens + # generate instances of all implemented codes to make sure nothing skips being checked # We do not include smaller random circuit code because some of them has a bad distance and fails the TableDecoder test @@ -14,13 +18,52 @@ random_circuit_code_args = vcat( [map(f -> getfield(random_all_to_all_circuit_code(c...), f), fieldnames(CircuitCode)) for c in random_all_to_all_circuit_args] ) +# test codes LP04 and LP118 from Appendix A of [raveendran2022finite](@cite), +B04 = Dict( + 7 => [0 0 0 0; 0 1 2 5; 0 6 3 1], + 9 => [0 0 0 0; 0 1 6 7; 0 4 5 2], + 17 => [0 0 0 0; 0 1 2 11; 0 8 12 13], + 19 => [0 0 0 0; 0 2 6 9; 0 16 7 11] +) + +B118 = Dict( + 16 => [0 0 0 0 0; 0 2 4 7 11; 0 3 10 14 15], + 21 => [0 0 0 0 0; 0 4 5 7 17; 0 14 18 12 11], + 30 => [0 0 0 0 0; 0 2 14 24 25; 0 16 11 14 13], +) + +LP04 = [LPCode(base_matrix, l .- base_matrix', l) for (l, base_matrix) in B04] +LP118 = [LPCode(base_matrix, l .- base_matrix', l) for (l, base_matrix) in B118] + +# generalized bicyle codes from from Appendix B of [panteleev2021degenerate](@cite). +test_gb_codes = [ + generalized_bicycle_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], 127), + generalized_bicycle_codes([0, 1, 14, 16, 22], [0, 3, 13, 20, 42], 63), +] + +other_lifted_product_codes = [] + +# from Eq. (18) in Appendix A of [raveendran2022finite](@cite) +l = 63 +GA = group_algebra(GF(2), abelian_group(l)) +A = zeros(GA, 7, 7) +x = gens(GA)[] +A[LinearAlgebra.diagind(A)] .= x^27 +A[LinearAlgebra.diagind(A, -1)] .= x^54 +A[LinearAlgebra.diagind(A, 6)] .= x^54 +A[LinearAlgebra.diagind(A, -2)] .= GA(1) +A[LinearAlgebra.diagind(A, 5)] .= GA(1) +B = reshape([1 + x + x^6], (1, 1)) +push!(other_lifted_product_codes, LPCode(A, B)) + const code_instance_args = Dict( Toric => [(3,3), (4,4), (3,6), (4,3), (5,5)], Surface => [(3,3), (4,4), (3,6), (4,3), (5,5)], Gottesman => [3, 4, 5], CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4, 4)]), Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2, 2), Shor9())], - CircuitCode => random_circuit_code_args + CircuitCode => random_circuit_code_args, + LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes)) ) function all_testablable_code_instances(;maxn=nothing) diff --git a/test/test_ecc_codeproperties.jl b/test/test_ecc_codeproperties.jl index 404592763..523606422 100644 --- a/test/test_ecc_codeproperties.jl +++ b/test/test_ecc_codeproperties.jl @@ -30,10 +30,9 @@ H = parity_checks(code) @test nqubits(code) == size(H, 2) == code_n(code) @test size(H, 1) == code_s(code) - @test code_s(code) + code_k(code) == code_n(code) - @test size(H, 1) < size(H, 2) + @test code_s(code) + code_k(code) >= code_n(code) # possibly exist redundant checks _, _, rank = canonicalize!(copy(H), ranks=true) - @test rank == size(H, 1) # TODO maybe weaken this if we want to permit codes with redundancies + @test rank <= size(H, 1) @test QuantumClifford.stab_looks_good(copy(H)) end end diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index f6ceee981..4a8382c17 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -96,3 +96,30 @@ end end end + +@testset "belief prop decoders, good for sparse codes" begin + codes = vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes) + + noise = 0.001 + + setups = [ + CommutationCheckECCSetup(noise), + NaiveSyndromeECCSetup(noise, 0), + ShorSyndromeECCSetup(noise, 0), + ] + # lifted product codes currently trigger errors in syndrome circuits + + for c in codes + for s in setups + for d in [c -> PyBeliefPropOSDecoder(c, maxiter=10)] + nsamples = code_n(c) > 400 ? 1000 : 100000 + # take fewer samples for larger codes to save time + e = evaluate_decoder(d(c), s, nsamples) + # @show c + # @show s + # @show e + @assert max(e...) < noise / 4 (c, s, e) + end + end + end +end diff --git a/test/test_ecc_encoding.jl b/test/test_ecc_encoding.jl index cab0562ff..78312c57d 100644 --- a/test/test_ecc_encoding.jl +++ b/test/test_ecc_encoding.jl @@ -31,7 +31,7 @@ pre_encₖ = one(Stabilizer, code_k(code)) # n-k ancillary qubits in state zero prepended - pre_encₙ = one(Stabilizer, code_s(code)) ⊗ pre_encₖ + pre_encₙ = one(Stabilizer, code_n(code) - code_k(code)) ⊗ pre_encₖ # running the encoding circuit encodedₙ = mctrajectory!(pre_encₙ, circ)[1] |> canonicalize! diff --git a/test/test_ecc_syndromes.jl b/test/test_ecc_syndromes.jl index 45f4e0f89..0f6f67ea0 100644 --- a/test/test_ecc_syndromes.jl +++ b/test/test_ecc_syndromes.jl @@ -17,8 +17,8 @@ # no noise naive_frames = PauliFrame(nframes, naive_qubits, syndromebits) shor_frames = PauliFrame(nframes, shor_qubits, last(shor_bits)) - naive_circuit = [ecirc..., naive_scirc...] - shor_circuit = [ecirc..., shor_cat_scirc..., shor_scirc...] + naive_circuit = vcat(ecirc, naive_scirc) + shor_circuit = vcat(ecirc, shor_cat_scirc, shor_scirc) pftrajectories(naive_frames, naive_circuit) pftrajectories(shor_frames, shor_circuit) @test pfmeasurements(naive_frames) == pfmeasurements(shor_frames)[:,shor_bits] @@ -27,7 +27,7 @@ naive_frames = PauliFrame(nframes, naive_qubits, syndromebits) shor_frames = PauliFrame(nframes, shor_qubits, last(shor_bits)) pftrajectories(naive_frames, ecirc) - pftrajectories(shor_frames, [ecirc..., shor_cat_scirc...]) + pftrajectories(shor_frames, vcat(ecirc, shor_cat_scirc)) # manually injecting the same type of noise in the frames -- not really a user accessible API p = random_pauli(dataqubits, realphase=true) pₙ = embed(naive_qubits, 1:dataqubits, p) diff --git a/test/test_jet.jl b/test/test_jet.jl index 304f3b78c..1b8b01504 100644 --- a/test/test_jet.jl +++ b/test/test_jet.jl @@ -8,6 +8,7 @@ using LinearAlgebra using Nemo using AbstractAlgebra + using Hecke rep = report_package("QuantumClifford"; ignored_modules=( @@ -19,6 +20,7 @@ AnyFrameModule(LinearAlgebra), AnyFrameModule(Nemo), AnyFrameModule(AbstractAlgebra), + AnyFrameModule(Hecke), )) @show rep From 01cc0f09d17130b3ab1aac9f393c826f0ad8c5fe Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 26 Sep 2024 17:31:45 -0400 Subject: [PATCH 32/66] bump version number and update CHANGELOG --- CHANGELOG.md | 7 +++---- Project.toml | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 762c48595..30cdbd09e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,15 +5,14 @@ # News -## dev +## v0.9.10 - 2024-09-26 +- The lifted product class of quantum LDPC codes is implemented in the ECC submodule. +- **(fix)** `ECC.code_s` now gives the number of parity checks with redundancy. If you want the number of linearly independent parity checks, you can use `LinearAlgebra.rank`. - Implementing many more named single-qubit gates following naming convention similar to the stim package in python. - **(fix)** Bug fix to the `parity_checks(ReedMuller(r, m))` of classical Reed-Muller code (it was returning generator matrix). - `RecursiveReedMuller` code implementation as an alternative implementation of `ReedMuller`. -## v0.9.10 - 2024-09-26 - -- **(fix)** `ECC.code_s` now gives the number of parity checks with redundancy. If you want the number of linearly independent parity checks, you can use `LinearAlgebra.rank`. ## v0.9.9 - 2024-08-05 diff --git a/Project.toml b/Project.toml index 802c43685..02555d45f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.9" +version = "0.9.10" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" From bb4230911507b5d93038b23f10ea96cceb72e073 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2024 22:21:20 -0400 Subject: [PATCH 33/66] CompatHelper: bump compat for Nemo to 0.47, (keep existing compat) (#363) Co-authored-by: CompatHelper Julia Co-authored-by: Stefan Krastanov --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 02555d45f..9fa6de810 100644 --- a/Project.toml +++ b/Project.toml @@ -56,7 +56,7 @@ LDPCDecoders = "0.3.1" LinearAlgebra = "1.9" MacroTools = "0.5.9" Makie = "0.20, 0.21" -Nemo = "^0.42.1, 0.43, 0.44, 0.45, 0.46" +Nemo = "0.42.1, 0.43, 0.44, 0.45, 0.46, 0.47" Plots = "1.38.0" PrecompileTools = "1.2" PyQDecoders = "0.2.1" From 6431659d85b6912e2e743e6fe1ddcfcd5dfaec62 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2024 22:23:04 -0400 Subject: [PATCH 34/66] CompatHelper: bump compat for Hecke in [weakdeps] to 0.34, (keep existing compat) (#370) Co-authored-by: CompatHelper Julia --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 9fa6de810..003daf2d5 100644 --- a/Project.toml +++ b/Project.toml @@ -48,7 +48,7 @@ Combinatorics = "1.0" DataStructures = "0.18" DocStringExtensions = "0.9" Graphs = "1.9" -Hecke = "0.28, 0.29, 0.30, 0.31, 0.32, 0.33" +Hecke = "0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34" HostCPUFeatures = "0.1.6" ILog2 = "0.2.3" InteractiveUtils = "1.9" From e5b7bd55972799640877d846b34e607cc8cf5bfe Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Fri, 27 Sep 2024 08:23:27 +0500 Subject: [PATCH 35/66] some performance optimizations in decoders (#369) --------- Co-authored-by: Stefan Krastanov --- .../QuantumCliffordLDPCDecodersExt.jl | 8 ++++---- .../QuantumCliffordPyQDecodersExt.jl | 13 +++++++------ src/ecc/decoder_pipeline.jl | 19 ++++++++++++++----- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl b/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl index 8c369cf82..e80c13af6 100644 --- a/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl +++ b/ext/QuantumCliffordLDPCDecodersExt/QuantumCliffordLDPCDecodersExt.jl @@ -74,16 +74,16 @@ parity_checks(d::BeliefPropDecoder) = d.H parity_checks(d::BitFlipDecoder) = d.H function decode(d::BeliefPropDecoder, syndrome_sample) - row_x = syndrome_sample[1:d.cx] - row_z = syndrome_sample[d.cx+1:d.cx+d.cz] + row_x = @view syndrome_sample[1:d.cx] + row_z = @view syndrome_sample[d.cx+1:d.cx+d.cz] guess_z, success = LDPCDecoders.decode!(d.bpdecoderx, row_x) guess_x, success = LDPCDecoders.decode!(d.bpdecoderz, row_z) return vcat(guess_x, guess_z) end function decode(d::BitFlipDecoder, syndrome_sample) - row_x = syndrome_sample[1:d.cx] - row_z = syndrome_sample[d.cx+1:d.cx+d.cz] + row_x = @view syndrome_sample[1:d.cx] + row_z = @view syndrome_sample[d.cx+1:d.cx+d.cz] guess_z, success = LDPCDecoders.decode!(d.bfdecoderx, row_x) guess_x, success = LDPCDecoders.decode!(d.bfdecoderz, row_z) return vcat(guess_x, guess_z) diff --git a/ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl b/ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl index bfa557d05..e3496a733 100644 --- a/ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl +++ b/ext/QuantumCliffordPyQDecodersExt/QuantumCliffordPyQDecodersExt.jl @@ -69,8 +69,8 @@ end parity_checks(d::PyBP) = d.H function decode(d::PyBP, syndrome_sample) - row_x = syndrome_sample[1:d.nx] # TODO These copies and indirections might be costly! - row_z = syndrome_sample[d.nx+1:end] + row_x = @view syndrome_sample[1:d.nx] + row_z = @view syndrome_sample[d.nx+1:end] guess_z_errors = PythonCall.PyArray(d.pyx.decode(np.array(row_x))) guess_x_errors = PythonCall.PyArray(d.pyz.decode(np.array(row_z))) vcat(guess_x_errors, guess_z_errors) @@ -106,18 +106,19 @@ end parity_checks(d::PyMatchingDecoder) = d.H function decode(d::PyMatchingDecoder, syndrome_sample) - row_x = syndrome_sample[1:d.nx] # TODO This copy is costly! - row_z = syndrome_sample[d.nx+1:end] + row_x = @view syndrome_sample[1:d.nx] + row_z = @view syndrome_sample[d.nx+1:end] guess_z_errors = PythonCall.PyArray(d.pyx.decode(row_x)) guess_x_errors = PythonCall.PyArray(d.pyz.decode(row_z)) vcat(guess_x_errors, guess_z_errors) end function batchdecode(d::PyMatchingDecoder, syndrome_samples) - row_x = syndrome_samples[:,1:d.nx] # TODO This copy is costly! - row_z = syndrome_samples[:,d.nx+1:end] + row_x = @view syndrome_samples[:,1:d.nx] + row_z = @view syndrome_samples[:,d.nx+1:end] guess_z_errors = PythonCall.PyArray(d.pyx.decode_batch(row_x)) guess_x_errors = PythonCall.PyArray(d.pyz.decode_batch(row_z)) + n_cols_x = size(guess_x_errors, 2) hcat(guess_x_errors, guess_z_errors) end diff --git a/src/ecc/decoder_pipeline.jl b/src/ecc/decoder_pipeline.jl index 0488dd28a..3ac2680cf 100644 --- a/src/ecc/decoder_pipeline.jl +++ b/src/ecc/decoder_pipeline.jl @@ -171,12 +171,21 @@ end function evaluate_guesses(measured_faults, guesses, faults_matrix) nsamples = size(guesses, 1) - guess_faults = (faults_matrix * guesses') .% 2 # TODO this can be faster and non-allocating by turning it into a loop - decoded = 0 - for i in 1:nsamples # TODO this can be faster and non-allocating by having the loop and the matrix multiplication on the line above work together and not store anything - (@view guess_faults[:,i]) == (@view measured_faults[i,:]) && (decoded += 1) + fails = 0 + for i in 1:nsamples + for j in 1:size(faults_matrix, 1) + sum_mod = 0 + @inbounds @simd for k in 1:size(faults_matrix, 2) + sum_mod += faults_matrix[j, k] * guesses[i, k] + end + sum_mod %= 2 + if sum_mod != measured_faults[i, j] + fails += 1 + break + end + end end - return (nsamples - decoded) / nsamples + return fails / nsamples end function evaluate_decoder(d::AbstractSyndromeDecoder, setup::CommutationCheckECCSetup, nsamples::Int) From 8173b45ad4fbc417d24cd10516f86a0d123a02e8 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Fri, 27 Sep 2024 08:59:39 +0500 Subject: [PATCH 36/66] =?UTF-8?q?Type=20promotion=20between=20MixedDestabi?= =?UTF-8?q?lizer,=20Stabilizer=20and=20others,=20also=20used=20in=20?= =?UTF-8?q?=E2=8A=97=20(#354)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Co-authored-by: Stefan Krastanov --- src/QuantumClifford.jl | 10 ++++++++++ src/linalg.jl | 6 ++++-- test/test_stabs.jl | 6 ++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index ffe679a0d..6f22a6fff 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -989,6 +989,16 @@ function _apply!(stab::AbstractStabilizer, p::PauliOperator, indices; phases::Va stab end +############################## +# Conversion and promotion +############################## + +Base.promote_rule(::Type{<:Destabilizer{T}} , ::Type{<:MixedDestabilizer{T}}) where {T<:Tableau} = MixedDestabilizer{T} +Base.promote_rule(::Type{<:MixedStabilizer{T}}, ::Type{<:MixedDestabilizer{T}}) where {T<:Tableau} = MixedDestabilizer{T} +Base.promote_rule(::Type{<:Stabilizer{T}} , ::Type{<:S} ) where {T<:Tableau, S<:Union{MixedStabilizer{T}, Destabilizer{T}, MixedDestabilizer{T}}} = S + +Base.convert(::Type{<:MixedDestabilizer{T}}, x::Union{Destabilizer{T}, MixedStabilizer{T}, Stabilizer{T}}) where {T <: Tableau} = MixedDestabilizer(x) + ############################## # Helpers for binary codes ############################## diff --git a/src/linalg.jl b/src/linalg.jl index 16b4eaf52..b95fba888 100644 --- a/src/linalg.jl +++ b/src/linalg.jl @@ -161,8 +161,10 @@ julia> tensor(s, s) See also [`tensor_pow`](@ref).""" function tensor end -function tensor(ops::AbstractStabilizer...) # TODO optimize this by doing conversion to common type to enable preallocation - foldl(⊗, ops[2:end], init=ops[1]) +function tensor(ops::AbstractStabilizer...) # TODO optimize by pre-allocating one large tableau instead of the current quadratic fold + ct = promote_type(map(typeof, ops)...) + conv_ops = map(x -> convert(ct, x), ops) + return foldl(⊗, conv_ops) end """Repeated tensor product of an operators or a tableau. diff --git a/test/test_stabs.jl b/test/test_stabs.jl index 63a5c562e..d415621ea 100644 --- a/test/test_stabs.jl +++ b/test/test_stabs.jl @@ -55,6 +55,12 @@ stabs = [s[1:i] for s in [random_stabilizer(n) for n in [32,16,16,64,63,65,129,128,127]] for i in rand(1:10)]; mdstabs = MixedDestabilizer.(stabs); @test canonicalize!(⊗(stabs...)) == canonicalize!(stabilizerview(⊗(mdstabs...))) + md = MixedDestabilizer(random_destabilizer(n)) + s = random_stabilizer(n) + mds = md⊗s + @test mixed_destab_looks_good(mds) + estab = stabilizerview(md)⊗s + @test canonicalize!(copy(stabilizerview(mds))) == canonicalize!(estab) end end From a9af4b01e8e4db6e9702bacf14325827f27a4837 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Fri, 27 Sep 2024 18:41:00 +0500 Subject: [PATCH 37/66] Horizontal concatenation (`hcat`) for Tableaux and Stabilizers (#304) --------- Co-authored-by: Fe-r-oz Co-authored-by: Stefan Krastanov Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- src/QuantumClifford.jl | 49 +++++++++++++++++++++++++++++++++++++++++- test/test_jet.jl | 2 +- test/test_stabs.jl | 11 ++++++++++ 5 files changed, 65 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30cdbd09e..3d3d071bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ # News +## v0.9.11-dev + +- `hcat` of Tableaux objects + ## v0.9.10 - 2024-09-26 - The lifted product class of quantum LDPC codes is implemented in the ECC submodule. diff --git a/Project.toml b/Project.toml index 003daf2d5..3b27ca4ee 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.10" +version = "0.9.11-dev" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 6f22a6fff..60e020440 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -935,6 +935,19 @@ function check_allrowscommute(stabilizer::Tableau) end check_allrowscommute(stabilizer::Stabilizer)=check_allrowscommute(tab(stabilizer)) +""" +Vertically concatenates tableaux. + +```jldoctest +julia> vcat(ghz(2), ghz(2)) ++ XX ++ ZZ ++ XX ++ ZZ +``` + +See also: [`hcat`](@ref) +""" function Base.vcat(tabs::Tableau...) Tableau( vcat((s.phases for s in tabs)...), @@ -942,7 +955,41 @@ function Base.vcat(tabs::Tableau...) hcat((s.xzs for s in tabs)...)) end -Base.vcat(stabs::Stabilizer...) = Stabilizer(vcat((tab(s) for s in stabs)...)) +Base.vcat(stabs::Stabilizer{T}...) where {T} = Stabilizer(vcat((tab(s) for s in stabs)...)) + +""" +Horizontally concatenates tableaux. + +```jldoctest +julia> hcat(ghz(2), ghz(2)) ++ XXXX ++ ZZZZ +``` + +See also: [`vcat`](@ref) +""" +function Base.hcat(tabs::Tableau...) # TODO this implementation is slow as it unpacks each bitvector into bits and repacks them -- reuse the various tableau inset functionality we have to speed this up + rows = size(tabs[1], 1) + cols = sum(map(nqubits, tabs)) + newtab = zero(Tableau, rows, cols) + cols_idx = 1 + for tab in tabs + rows_tab, cols_tab = size(tab) + if rows_tab != rows + throw(ArgumentError("All input Tableaux/Stabilizers must have the same number of rows.")) + end + for i in 1:rows + for j in 1:cols_tab + newtab[i, cols_idx+j-1]::Tuple{Bool,Bool} = tab[i, j]::Tuple{Bool,Bool} + end + newtab.phases[i] = (newtab.phases[i]+tab.phases[i])%4 + end + cols_idx += cols_tab + end + return newtab +end + +Base.hcat(stabs::Stabilizer{T}...) where {T} = Stabilizer(hcat((tab(s) for s in stabs)...)) ############################## # Unitary Clifford Operations diff --git a/test/test_jet.jl b/test/test_jet.jl index 1b8b01504..42454e54e 100644 --- a/test/test_jet.jl +++ b/test/test_jet.jl @@ -25,5 +25,5 @@ @show rep @test_broken length(JET.get_reports(rep)) == 0 - @test length(JET.get_reports(rep)) <= 11 + @test length(JET.get_reports(rep)) <= 10 end diff --git a/test/test_stabs.jl b/test/test_stabs.jl index d415621ea..b479934c2 100644 --- a/test/test_stabs.jl +++ b/test/test_stabs.jl @@ -96,4 +96,15 @@ @test stab_to_gf2(s2a) == stab_to_gf2(s2b) end end + + @testset "horizontal concatenation" begin + @test hcat(ghz(2), ghz(2)) == S"XXXX ZZZZ" + s1 = S"YZ -XX" + s2 = S"-ZY -YX" + @test hcat(copy(s1), copy(s2)) == S"-YZZY XXYX" + @test hcat(copy(s1), copy(s2), copy(s1), copy(s2)) == S"YZZYYZZY XXYXXXYX" + @test_throws ArgumentError hcat(copy(s1), random_stabilizer(3)) + @test hcat(copy(tab(s1)), copy(tab(s2))) == T"-YZZY XXYX" + @test hcat(copy(tab(s1)), copy(tab(s2)), copy(tab(s1)), copy(tab(s2))) == T"YZZYYZZY XXYXXXYX" + end end From f42083465dcbe92f1ed5eeac6e6b7d3fd3d61e36 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Fri, 27 Sep 2024 18:46:29 +0500 Subject: [PATCH 38/66] =?UTF-8?q?`[[2=E1=B5=90=20-=201,=201,=203]]`=20quan?= =?UTF-8?q?tum=20Reed-Muller=20code=20(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Co-authored-by: Fe-r-oz Co-authored-by: Stefan Krastanov Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 1 + docs/src/references.bib | 33 +++++++++++++++++++ docs/src/references.md | 3 ++ src/ecc/ECC.jl | 4 +-- src/ecc/codes/quantumreedmuller.jl | 41 +++++++++++++++++++++++ test/test_ecc_base.jl | 3 +- test/test_ecc_decoder_all_setups.jl | 27 ++++++++++++++++ test/test_ecc_quantumreedmuller.jl | 50 +++++++++++++++++++++++++++++ 8 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 src/ecc/codes/quantumreedmuller.jl create mode 100644 test/test_ecc_quantumreedmuller.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d3d071bb..4ee772527 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ## v0.9.11-dev - `hcat` of Tableaux objects +- `QuantumReedMuller` codes added to the ECC module ## v0.9.10 - 2024-09-26 diff --git a/docs/src/references.bib b/docs/src/references.bib index 1cbde2910..29500a034 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -454,3 +454,36 @@ @article{raveendran2022finite issn = {2521-327X}, doi = {10.22331/q-2022-07-20-767}, } + +@article{steane1999quantum, + title={Quantum reed-muller codes}, + author={Steane, Andrew M}, + journal={IEEE Transactions on Information Theory}, + volume={45}, + number={5}, + pages={1701--1703}, + year={1999}, + publisher={IEEE} +} + +@article{campbell2012magic, + title={Magic-state distillation in all prime dimensions using quantum reed-muller codes}, + author={Campbell, Earl T and Anwar, Hussain and Browne, Dan E}, + journal={Physical Review X}, + volume={2}, + number={4}, + pages={041021}, + year={2012}, + publisher={APS} +} + +@article{anderson2014fault, + title={Fault-tolerant conversion between the steane and reed-muller quantum codes}, + author={Anderson, Jonas T and Duclos-Cianci, Guillaume and Poulin, David}, + journal={Physical review letters}, + volume={113}, + number={8}, + pages={080501}, + year={2014}, + publisher={APS} +} diff --git a/docs/src/references.md b/docs/src/references.md index b37e94d4e..35e944a21 100644 --- a/docs/src/references.md +++ b/docs/src/references.md @@ -37,6 +37,9 @@ For quantum code construction routines: - [kitaev2003fault](@cite) - [fowler2012surface](@cite) - [knill1996concatenated](@cite) +- [steane1999quantum](@cite) +- [campbell2012magic](@cite) +- [anderson2014fault](@cite) For classical code construction routines: - [muller1954application](@cite) diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index d8e863136..cdda7742e 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -20,7 +20,7 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, RepCode, LiftedCode, CSS, Shor9, Steane7, Cleve8, Perfect5, Bitflip3, - Toric, Gottesman, Surface, Concat, CircuitCode, + Toric, Gottesman, Surface, Concat, CircuitCode, QuantumReedMuller, LPCode, two_block_group_algebra_codes, generalized_bicycle_codes, bicycle_codes, random_brickwork_circuit_code, random_all_to_all_circuit_code, evaluate_decoder, @@ -376,10 +376,10 @@ include("codes/gottesman.jl") include("codes/surface.jl") include("codes/concat.jl") include("codes/random_circuit.jl") - include("codes/classical/reedmuller.jl") include("codes/classical/recursivereedmuller.jl") include("codes/classical/bch.jl") +include("codes/quantumreedmuller.jl") # qLDPC include("codes/classical/lifted.jl") diff --git a/src/ecc/codes/quantumreedmuller.jl b/src/ecc/codes/quantumreedmuller.jl new file mode 100644 index 000000000..6813d6646 --- /dev/null +++ b/src/ecc/codes/quantumreedmuller.jl @@ -0,0 +1,41 @@ +""" +The family of `[[2ᵐ - 1, 1, 3]]` CSS Quantum-Reed-Muller codes, as discovered by Steane in his 1999 paper [steane1999quantum](@cite). + +Quantum codes are constructed from shortened Reed-Muller codes `RM(1, m)`, by removing the first row and column of the generator matrix `Gₘ`. Similarly, we can define truncated dual codes `RM(m - 2, m)` using the generator matrix `Hₘ` [anderson2014fault](@cite). The quantum Reed-Muller codes `QRM(m)` derived from `RM(1, m)` are CSS codes. + +Given that the stabilizers of the quantum code are defined through the generator matrix of the classical code, the minimum distance of the quantum code corresponds to the minimum distance of the dual classical code, which is `d = 3`, thus it can correct any single qubit error. Since one stabilizer from the original and one from the dual code are removed in the truncation process, the code parameters are `[[2ᵐ - 1, 1, 3]]`. + +You might be interested in consulting [anderson2014fault](@cite) and [campbell2012magic](@cite) as well. + +The ECC Zoo has an [entry for this family](https://errorcorrectionzoo.org/c/quantum_reed_muller). +""" +struct QuantumReedMuller <: AbstractECC + m::Int + function QuantumReedMuller(m) + if m < 3 + throw(DomainError("Invalid parameters: m must be bigger than 2 in order to have a valid code.")) + end + new(m) + end +end + +function iscss(::Type{QuantumReedMuller}) + return true +end + +function parity_checks(c::QuantumReedMuller) + RM₁₋ₘ = generator(RecursiveReedMuller(1, c.m)) + RM₍ₘ₋₂₎₋ₘ₎ = generator(RecursiveReedMuller(c.m-2, c.m)) + QRM = CSS(RM₁₋ₘ[2:end, 2:end], RM₍ₘ₋₂₎₋ₘ₎[2:end, 2:end]) + Stabilizer(QRM) +end + +code_n(c::QuantumReedMuller) = 2^c.m - 1 + +code_k(c::QuantumReedMuller) = 1 + +distance(c::QuantumReedMuller) = 3 + +parity_checks_x(c::QuantumReedMuller) = stab_to_gf2(parity_checks(QuantumReedMuller(c.m)))[1:c.m, 1:end÷2] + +parity_checks_z(c::QuantumReedMuller) = stab_to_gf2(parity_checks(QuantumReedMuller(c.m)))[end-(code_n(c::QuantumReedMuller)-2-c.m):end, end÷2+1:end] diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index d9afc09bb..6dd7e5162 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -63,7 +63,8 @@ const code_instance_args = Dict( CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4, 4)]), Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2, 2), Shor9())], CircuitCode => random_circuit_code_args, - LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes)) + LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes)), + QuantumReedMuller => [3, 4, 5] ) function all_testablable_code_instances(;maxn=nothing) diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index 4a8382c17..bf479cd0b 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -60,6 +60,33 @@ end end + @testset "BitFlipDecoder decoder, good for sparse codes" begin + codes = [ + QuantumReedMuller(3), + QuantumReedMuller(4) + ] + + noise = 0.001 + + setups = [ + CommutationCheckECCSetup(noise), + NaiveSyndromeECCSetup(noise, 0), + ShorSyndromeECCSetup(noise, 0), + ] + + for c in codes + for s in setups + for d in [c->BitFlipDecoder(c, maxiter=10)] + e = evaluate_decoder(d(c), s, 100000) + #@show c + #@show s + #@show e + @assert max(e...) < noise/4 + end + end + end + end + ## using Test diff --git a/test/test_ecc_quantumreedmuller.jl b/test/test_ecc_quantumreedmuller.jl new file mode 100644 index 000000000..efb76918f --- /dev/null +++ b/test/test_ecc_quantumreedmuller.jl @@ -0,0 +1,50 @@ +@testitem "Quantum Reed-Muller" begin + using Test + using Nemo: echelon_form, matrix, GF + using LinearAlgebra + using QuantumClifford + using QuantumClifford: canonicalize!, Stabilizer, stab_to_gf2 + using QuantumClifford.ECC + using QuantumClifford.ECC: AbstractECC, QuantumReedMuller, Steane7, CSS + + function designed_distance(mat) + dist = 3 + for row in eachrow(mat) + count = sum(row) + if count < dist + return false + end + end + return true + end + + @testset "Test QuantumReedMuller(r,m) properties" begin + for m in 3:10 + stab = parity_checks(QuantumReedMuller(m)) + H = stab_to_gf2(stab) + @test designed_distance(H) == true + # QuantumReedMuller(3) is the Steane7 code. + @test canonicalize!(parity_checks(Steane7())) == parity_checks(QuantumReedMuller(3)) + @test code_n(QuantumReedMuller(m)) == 2^m - 1 + @test code_k(QuantumReedMuller(m)) == 1 + @test distance(QuantumReedMuller(m)) == 3 + @test H == stab_to_gf2(parity_checks(CSS(parity_checks_x(QuantumReedMuller(m)), parity_checks_z(QuantumReedMuller(m))))) + # [[15,1,3]] qrm code from table 1 of https://arxiv.org/pdf/1705.0010 + qrm₁₅₁₃ = S"ZIZIZIZIZIZIZIZ + IZZIIZZIIZZIIZZ + IIIZZZZIIIIZZZZ + IIIIIIIZZZZZZZZ + IIZIIIZIIIZIIIZ + IIIIZIZIIIIIZIZ + IIIIIZZIIIIIIZZ + IIIIIIIIIZZIIZZ + IIIIIIIIIIIZZZZ + IIIIIIIIZIZIZIZ + XIXIXIXIXIXIXIX + IXXIIXXIIXXIIXX + IIIXXXXIIIIXXXX + IIIIIIIXXXXXXXX" + @test canonicalize!(parity_checks(qrm₁₅₁₃)) == canonicalize!(parity_checks(QuantumReedMuller(4))) + end + end +end From 57a0069e1714cd5773ffd0b6e2cb1eb4027194b8 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Fri, 27 Sep 2024 18:47:03 +0500 Subject: [PATCH 39/66] use minimal phase formula for sInvZCrY (#340) --- src/symbolic_cliffords.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index 781609923..e73622217 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -323,8 +323,8 @@ end @qubitop2 YCY (x1⊻z2⊻x2, z1⊻x2⊻z2 , x1⊻x2⊻z1 , x1⊻z1⊻z2, ~iszero( (x1 & ~z1 & ~x2 & z2) | (~x1 & z1 & x2 & ~z2))) @qubitop2 YCZ (x1⊻x2 , x2⊻z1 , x2 , z2⊻x1⊻z1, ~iszero( (x2 & (x1 ⊻ z1) & (z2 ⊻ x1)) )) -@qubitop2 ZCrY (x1, x1⊻z1⊻x2⊻z2, x1⊻x2, x1⊻z2, ~iszero((x1 & ~z1 & x2) | (x1 & ~z1 & ~z2) | (x1 & x2 & ~z2))) -@qubitop2 InvZCrY (x1, x1⊻z1⊻x2⊻z2, x1⊻x2, x1⊻z2, ~iszero((x1 & z1 & ~x2 & ~z2) | (x1 & ~z1 & ~x2 & z2) | (x1 & z1 & ~x2 & z2) | (x1 & z1 & x2 & z2))) +@qubitop2 ZCrY (x1, x1⊻z1⊻x2⊻z2, x1⊻x2, x1⊻z2, ~iszero((x1 &~z1 & x2) | (x1 & ~z1 & ~z2) | (x1 & x2 & ~z2))) +@qubitop2 InvZCrY (x1, x1⊻z1⊻x2⊻z2, x1⊻x2, x1⊻z2, ~iszero((x1 & z1 &~x2) | (x1 & z1 & z2) | (x1 &~x2 & z2))) #= To get the boolean formulas for the phase, it is easiest to first write down the truth table for the phase: From 4e06c0184a6ccef4894bb3790310ce84cd6a54f5 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Fri, 27 Sep 2024 13:40:25 -0400 Subject: [PATCH 40/66] enable Lifted Product Code tests and reintroduce piracy (#371) * enable Lifted Product Code tests and reintroduce piracy * do not use reuse the "lift" name in a non-idiomatic-for-Nemo way * simplify the representation function call in LPCode --- CHANGELOG.md | 3 +- Project.toml | 2 +- .../QuantumCliffordHeckeExt.jl | 3 +- ext/QuantumCliffordHeckeExt/lifted.jl | 22 ++++--- ext/QuantumCliffordHeckeExt/lifted_product.jl | 10 +-- ext/QuantumCliffordHeckeExt/types.jl | 2 +- test/test_ecc_base.jl | 18 +++--- test/test_ecc_codeproperties.jl | 2 +- test/test_ecc_decoder_all_setups.jl | 64 ++++++------------- 9 files changed, 55 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ee772527..e3534ef23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,11 @@ # News -## v0.9.11-dev +## v0.9.11 - `hcat` of Tableaux objects - `QuantumReedMuller` codes added to the ECC module +- **(breaking)** change the convention for how to provide a representation function in the constructor of `LPCode` -- strictly speaking a breaking change, but this is not an API that is publicly used in practice ## v0.9.10 - 2024-09-26 diff --git a/Project.toml b/Project.toml index 3b27ca4ee..d8f9e83e4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.11-dev" +version = "0.9.11" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl index 354de8a69..29e9de8ce 100644 --- a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl +++ b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl @@ -6,7 +6,8 @@ import QuantumClifford, LinearAlgebra import Hecke: Group, GroupElem, AdditiveGroup, AdditiveGroupElem, GroupAlgebra, GroupAlgebraElem, FqFieldElem, representation_matrix, dim, base_ring, multiplication_table, coefficients, abelian_group, group_algebra -import Nemo: characteristic, matrix_repr, GF, ZZ +import Nemo +import Nemo: characteristic, matrix_repr, GF, ZZ, lift import QuantumClifford.ECC: AbstractECC, CSS, ClassicalCode, hgp, code_k, code_n, code_s, iscss, parity_checks, parity_checks_x, parity_checks_z, parity_checks_xz, diff --git a/ext/QuantumCliffordHeckeExt/lifted.jl b/ext/QuantumCliffordHeckeExt/lifted.jl index 6601ce236..75964f983 100644 --- a/ext/QuantumCliffordHeckeExt/lifted.jl +++ b/ext/QuantumCliffordHeckeExt/lifted.jl @@ -22,10 +22,11 @@ The default `GA` is the group algebra of `A[1, 1]`, the default representation ` ## The representation function `repr` -In this struct, we use the default representation function `default_repr` to convert a `GF(2)`-group algebra element to a binary matrix. +We use the default representation function `Hecke.representation_matrix` to convert a `GF(2)`-group algebra element to a binary matrix. The default representation, provided by `Hecke`, is the permutation representation. -We also accept a custom representation function. +We also accept a custom representation function (the `repr` field of the constructor). +Whatever the representation, the matrix elements need to be convertible to Integers (e.g. permit `lift(ZZ, ...)`). Such a customization would be useful to reduce the number of bits required by the code construction. For example, if we use a D4 group for lifting, our default representation will be `8×8` permutation matrices, @@ -54,14 +55,12 @@ struct LiftedCode <: ClassicalCode end end -default_repr(y::GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}) = Matrix((x -> Bool(Int(lift(ZZ, x)))).(representation_matrix(y))) - """ `LiftedCode` constructor using the default `GF(2)` representation (coefficients converted to a permutation matrix by `representation_matrix` provided by Hecke). """ # TODO doctest example function LiftedCode(A::Matrix{GroupAlgebraElem{FqFieldElem, <: GroupAlgebra}}; GA::GroupAlgebra=parent(A[1,1])) !(characteristic(base_ring(A[1, 1])) == 2) && error("The default permutation representation applies only to GF(2) group algebra; otherwise, a custom representation function should be provided") - LiftedCode(A; GA=GA, repr=default_repr) + LiftedCode(A; GA=GA, repr=representation_matrix) end # TODO document and doctest example @@ -71,7 +70,7 @@ function LiftedCode(group_elem_array::Matrix{<: GroupOrAdditiveGroupElem}; GA::G A[i, j] = GA[A[i, j]] end if repr === nothing - return LiftedCode(A; GA=GA, repr=default_repr) + return LiftedCode(A; GA=GA, repr=representation_matrix) else return LiftedCode(A; GA=GA, repr=repr) end @@ -85,11 +84,16 @@ function LiftedCode(shift_array::Matrix{Int}, l::Int; GA::GroupAlgebra=group_alg A[i, j] = GA[shift_array[i, j]%l+1] end end - return LiftedCode(A; GA=GA, repr=default_repr) + return LiftedCode(A; GA=GA, repr=representation_matrix) end -function lift(repr::Function, mat::GroupAlgebraElemMatrix) - vcat([hcat([repr(mat[i, j]) for j in axes(mat, 2)]...) for i in axes(mat, 1)]...) +lift_to_bool(x) = Bool(Int(lift(ZZ,x))) + +function concat_lift_repr(repr, mat) + x = repr.(mat) + y = hvcat(size(x,2), transpose(x)...) + z = Matrix(lift_to_bool.(y)) + return z end function parity_checks(c::LiftedCode) diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index aecad36b3..97e41b044 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -72,7 +72,7 @@ julia> code_n(c2), code_k(c2) ## The representation function -In this struct, we use the default representation function `default_repr` to convert a `GF(2)`-group algebra element to a binary matrix. +We use the default representation function `Hecke.representation_matrix` to convert a `GF(2)`-group algebra element to a binary matrix. The default representation, provided by `Hecke`, is the permutation representation. We also accept a custom representation function as detailed in [`LiftedCode`](@ref). @@ -107,24 +107,24 @@ end # TODO document and doctest example function LPCode(A::FqFieldGroupAlgebraElemMatrix, B::FqFieldGroupAlgebraElemMatrix; GA::GroupAlgebra=parent(A[1,1])) - LPCode(LiftedCode(A; GA=GA, repr=default_repr), LiftedCode(B; GA=GA, repr=default_repr); GA=GA, repr=default_repr) + LPCode(LiftedCode(A; GA=GA, repr=representation_matrix), LiftedCode(B; GA=GA, repr=representation_matrix); GA=GA, repr=representation_matrix) end # TODO document and doctest example function LPCode(group_elem_array1::Matrix{<: GroupOrAdditiveGroupElem}, group_elem_array2::Matrix{<: GroupOrAdditiveGroupElem}; GA::GroupAlgebra=group_algebra(GF(2), parent(group_elem_array1[1,1]))) - LPCode(LiftedCode(group_elem_array1; GA=GA), LiftedCode(group_elem_array2; GA=GA); GA=GA, repr=default_repr) + LPCode(LiftedCode(group_elem_array1; GA=GA), LiftedCode(group_elem_array2; GA=GA); GA=GA, repr=representation_matrix) end # TODO document and doctest example function LPCode(shift_array1::Matrix{Int}, shift_array2::Matrix{Int}, l::Int; GA::GroupAlgebra=group_algebra(GF(2), abelian_group(l))) - LPCode(LiftedCode(shift_array1, l; GA=GA), LiftedCode(shift_array2, l; GA=GA); GA=GA, repr=default_repr) + LPCode(LiftedCode(shift_array1, l; GA=GA), LiftedCode(shift_array2, l; GA=GA); GA=GA, repr=representation_matrix) end iscss(::Type{LPCode}) = true function parity_checks_xz(c::LPCode) hx, hz = hgp(c.A, c.B') - hx, hz = lift(c.repr, hx), lift(c.repr, hz) + hx, hz = concat_lift_repr(c.repr,hx), concat_lift_repr(c.repr,hz) return hx, hz end diff --git a/ext/QuantumCliffordHeckeExt/types.jl b/ext/QuantumCliffordHeckeExt/types.jl index 52ccb70dd..dd3845acc 100644 --- a/ext/QuantumCliffordHeckeExt/types.jl +++ b/ext/QuantumCliffordHeckeExt/types.jl @@ -15,7 +15,7 @@ Compute the adjoint of a group algebra element. The adjoint is defined as the conjugate of the element in the group algebra, i.e. the inverse of the element in the associated group. """ -function _adjoint(a::GroupAlgebraElem{T}) where T # TODO Is this used? Should it be deleted? +function Base.adjoint(a::GroupAlgebraElem{T}) where T # TODO we would like to use Base.adjoint, but that would be type piracy. Upstream this to Nemo or Hecke or AbstractAlgebra A = parent(a) d = dim(A) v = Vector{T}(undef, d) diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index 6dd7e5162..f087ea627 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -57,20 +57,20 @@ B = reshape([1 + x + x^6], (1, 1)) push!(other_lifted_product_codes, LPCode(A, B)) const code_instance_args = Dict( - Toric => [(3,3), (4,4), (3,6), (4,3), (5,5)], - Surface => [(3,3), (4,4), (3,6), (4,3), (5,5)], - Gottesman => [3, 4, 5], - CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4, 4)]), - Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2, 2), Shor9())], - CircuitCode => random_circuit_code_args, - LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes)), - QuantumReedMuller => [3, 4, 5] + :Toric => [(3,3), (4,4), (3,6), (4,3), (5,5)], + :Surface => [(3,3), (4,4), (3,6), (4,3), (5,5)], + :Gottesman => [3, 4, 5], + :CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4, 4)]), + :Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2, 2), Shor9())], + :CircuitCode => random_circuit_code_args, + :LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes)), + :QuantumReedMuller => [3, 4, 5] ) function all_testablable_code_instances(;maxn=nothing) codeinstances = [] for t in subtypes(QuantumClifford.ECC.AbstractECC) - for c in get(code_instance_args, t, []) + for c in get(code_instance_args, t.name.name, []) codeinstance = t(c...) !isnothing(maxn) && nqubits(codeinstance) > maxn && continue push!(codeinstances, codeinstance) diff --git a/test/test_ecc_codeproperties.jl b/test/test_ecc_codeproperties.jl index 523606422..ec529d3e4 100644 --- a/test/test_ecc_codeproperties.jl +++ b/test/test_ecc_codeproperties.jl @@ -33,7 +33,7 @@ @test code_s(code) + code_k(code) >= code_n(code) # possibly exist redundant checks _, _, rank = canonicalize!(copy(H), ranks=true) @test rank <= size(H, 1) - @test QuantumClifford.stab_looks_good(copy(H)) + @test QuantumClifford.stab_looks_good(copy(H), remove_redundant_rows=true) end end end diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index bf479cd0b..c98813a38 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -26,7 +26,7 @@ #@show c #@show s #@show e - @assert max(e...) < noise/4 + @test max(e...) < noise/4 end end end @@ -35,31 +35,36 @@ ## @testset "belief prop decoders, good for sparse codes" begin - codes = [ - # TODO - ] + codes = vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes) noise = 0.001 setups = [ - CommutationCheckECCSetup(noise), - NaiveSyndromeECCSetup(noise, 0), - ShorSyndromeECCSetup(noise, 0), - ] + CommutationCheckECCSetup(noise), + NaiveSyndromeECCSetup(noise, 0), + ShorSyndromeECCSetup(noise, 0), + ] + # lifted product codes currently trigger errors in syndrome circuits for c in codes for s in setups - for d in [c->PyBeliefPropOSDecoder(c, maxiter=10)] - e = evaluate_decoder(d(c), s, 100000) - @show c - @show s - @show e - @assert max(e...) < noise/4 + for d in [c -> PyBeliefPropOSDecoder(c, maxiter=2)] + nsamples = 10000 + if true + @test_broken false # TODO these are too slow to test in CI + continue + end + e = evaluate_decoder(d(c), s, nsamples) + # @show c + # @show s + # @show e + @test max(e...) <= noise end end end end + @testset "BitFlipDecoder decoder, good for sparse codes" begin codes = [ QuantumReedMuller(3), @@ -81,7 +86,7 @@ #@show c #@show s #@show e - @assert max(e...) < noise/4 + @test max(e...) < noise/4 end end end @@ -118,34 +123,7 @@ #@show c #@show s #@show e - @assert max(e...) < noise/5 - end - end - end -end - -@testset "belief prop decoders, good for sparse codes" begin - codes = vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes) - - noise = 0.001 - - setups = [ - CommutationCheckECCSetup(noise), - NaiveSyndromeECCSetup(noise, 0), - ShorSyndromeECCSetup(noise, 0), - ] - # lifted product codes currently trigger errors in syndrome circuits - - for c in codes - for s in setups - for d in [c -> PyBeliefPropOSDecoder(c, maxiter=10)] - nsamples = code_n(c) > 400 ? 1000 : 100000 - # take fewer samples for larger codes to save time - e = evaluate_decoder(d(c), s, nsamples) - # @show c - # @show s - # @show e - @assert max(e...) < noise / 4 (c, s, e) + @test max(e...) < noise/5 end end end From 2b8e81f6c54bc38dbe2496f8c22d368650a08784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20G=C3=B6ttgens?= Date: Wed, 9 Oct 2024 23:43:20 +0200 Subject: [PATCH 41/66] Increase JET failure count for julia 1.11 (#386) --- test/test_jet.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_jet.jl b/test/test_jet.jl index 42454e54e..91348c168 100644 --- a/test/test_jet.jl +++ b/test/test_jet.jl @@ -24,6 +24,7 @@ )) @show rep + @show length(JET.get_reports(rep)) @test_broken length(JET.get_reports(rep)) == 0 - @test length(JET.get_reports(rep)) <= 10 + @test length(JET.get_reports(rep)) <= 23 end From be50f9c42c3814353caf9724e3d6499677b78bab Mon Sep 17 00:00:00 2001 From: Rabqubit Date: Fri, 18 Oct 2024 15:57:59 +0900 Subject: [PATCH 42/66] fix incompatibility with Julia 1.11.1 and remove the adjoint piracy (#393) --------- Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 6 +++++- Project.toml | 2 +- ext/QuantumCliffordHeckeExt/lifted_product.jl | 17 ++++++++++++++--- ext/QuantumCliffordHeckeExt/types.jl | 7 +++---- test/test_ecc_base.jl | 8 ++++---- test/test_ecc_decoder_all_setups.jl | 1 - 6 files changed, 27 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3534ef23..e260d5c02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,11 @@ # News -## v0.9.11 +## v0.9.12 - 2024-10-18 + +- Minor compat fixes for julia 1.11 in the handling of `hgp` + +## v0.9.11 - 2024-09-27 - `hcat` of Tableaux objects - `QuantumReedMuller` codes added to the ECC module diff --git a/Project.toml b/Project.toml index d8f9e83e4..4d300bc66 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.11" +version = "0.9.12" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index 97e41b044..ef09eddbb 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -122,8 +122,19 @@ end iscss(::Type{LPCode}) = true +function hgp(h₁::GroupAlgebraElemMatrix, h₂::GroupAlgebraElemMatrix) + r₁, n₁ = size(h₁) + r₂, n₂ = size(h₂) + # here we use `permutdims` instead of `transpose` to avoid recursive call + # convert LinearAlgebra.I to Matrix to fix incompatibility with Julia 1.11.1 + # TODO the performance may be affected by this workaround for large codes + hx = hcat(kron(h₁, Matrix(LinearAlgebra.I(n₂))), kron(Matrix(LinearAlgebra.I(r₁)), permutedims(group_algebra_conj.(h₂)))) + hz = hcat(kron(Matrix(LinearAlgebra.I(n₁)), h₂), kron(permutedims(group_algebra_conj.(h₁)), Matrix(LinearAlgebra.I(r₂)))) + hx, hz +end + function parity_checks_xz(c::LPCode) - hx, hz = hgp(c.A, c.B') + hx, hz = hgp(c.A, permutedims(group_algebra_conj.(c.B))) hx, hz = concat_lift_repr(c.repr,hx), concat_lift_repr(c.repr,hz) return hx, hz end @@ -165,7 +176,7 @@ julia> c = generalized_bicycle_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], julia> code_n(c), code_k(c) (254, 28) ``` -""" # TODO doctest example +""" function generalized_bicycle_codes(a_shifts::Array{Int}, b_shifts::Array{Int}, l::Int) GA = group_algebra(GF(2), abelian_group(l)) a = sum(GA[n%l+1] for n in a_shifts) @@ -183,5 +194,5 @@ See also: [`two_block_group_algebra_codes`](@ref), [`generalized_bicycle_codes`] function bicycle_codes(a_shifts::Array{Int}, l::Int) GA = group_algebra(GF(2), abelian_group(l)) a = sum(GA[n÷l+1] for n in a_shifts) - two_block_group_algebra_codes(a, a') + two_block_group_algebra_codes(a, group_algebra_conj(a)) end diff --git a/ext/QuantumCliffordHeckeExt/types.jl b/ext/QuantumCliffordHeckeExt/types.jl index dd3845acc..ecd0d7a68 100644 --- a/ext/QuantumCliffordHeckeExt/types.jl +++ b/ext/QuantumCliffordHeckeExt/types.jl @@ -11,11 +11,10 @@ const FqFieldGroupAlgebraElemMatrix = Union{ } """ -Compute the adjoint of a group algebra element. -The adjoint is defined as the conjugate of the element in the group algebra, -i.e. the inverse of the element in the associated group. +Compute the conjugate of a group algebra element. +The conjugate is defined by inversing elements in the associated group. """ -function Base.adjoint(a::GroupAlgebraElem{T}) where T # TODO we would like to use Base.adjoint, but that would be type piracy. Upstream this to Nemo or Hecke or AbstractAlgebra +function group_algebra_conj(a::GroupAlgebraElem{T}) where T A = parent(a) d = dim(A) v = Vector{T}(undef, d) diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index f087ea627..9f4a5ec9e 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -35,15 +35,15 @@ B118 = Dict( LP04 = [LPCode(base_matrix, l .- base_matrix', l) for (l, base_matrix) in B04] LP118 = [LPCode(base_matrix, l .- base_matrix', l) for (l, base_matrix) in B118] -# generalized bicyle codes from from Appendix B of [panteleev2021degenerate](@cite). +# generalized bicyle codes from (A1) and (A2) Appendix B of [panteleev2021degenerate](@cite). test_gb_codes = [ - generalized_bicycle_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], 127), - generalized_bicycle_codes([0, 1, 14, 16, 22], [0, 3, 13, 20, 42], 63), + generalized_bicycle_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], 127), # (A1) [[254, 28, 14≤d≤20]] + generalized_bicycle_codes([0, 1, 14, 16, 22], [0, 3, 13, 20, 42], 63), # (A2) [[126, 28, 8]] ] other_lifted_product_codes = [] -# from Eq. (18) in Appendix A of [raveendran2022finite](@cite) +# [[882, 24, d≤24]] code from (B1) in Appendix B of [panteleev2021degenerate](@cite) l = 63 GA = group_algebra(GF(2), abelian_group(l)) A = zeros(GA, 7, 7) diff --git a/test/test_ecc_decoder_all_setups.jl b/test/test_ecc_decoder_all_setups.jl index c98813a38..ecd747da3 100644 --- a/test/test_ecc_decoder_all_setups.jl +++ b/test/test_ecc_decoder_all_setups.jl @@ -44,7 +44,6 @@ NaiveSyndromeECCSetup(noise, 0), ShorSyndromeECCSetup(noise, 0), ] - # lifted product codes currently trigger errors in syndrome circuits for c in codes for s in setups From 0ddc9e86c2007a6242648d48a4b0f22beed88a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20G=C3=B6ttgens?= Date: Sun, 20 Oct 2024 16:01:11 +0200 Subject: [PATCH 43/66] Fix issues reported by JET (#385) --------- Co-authored-by: Stefan Krastanov Co-authored-by: Stefan Krastanov --- .gitignore | 3 ++- src/QuantumClifford.jl | 38 +++++++++++++-------------- src/dense_cliffords.jl | 12 ++++----- src/ecc/circuits.jl | 1 + src/ecc/decoder_pipeline.jl | 2 +- src/fastmemlayout.jl | 16 ++++++------ src/linalg.jl | 2 +- src/mul_leftright.jl | 6 ----- src/pauli_operator.jl | 7 ++--- src/project_trace_reset.jl | 10 +++---- src/randoms.jl | 6 ++--- src/symbolic_cliffords.jl | 2 +- src/tableau_show.jl | 52 ++++++++++++++++++------------------- test/Project.toml | 1 + test/test_jet.jl | 5 ++-- test/test_noisycircuits.jl | 2 +- test/test_projections.jl | 30 ++++++++++----------- test/test_trace.jl | 2 +- 18 files changed, 98 insertions(+), 99 deletions(-) diff --git a/.gitignore b/.gitignore index 8bf2daafc..a61e78b68 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ LocalPreferences.toml */.*swp scratch/ *.cov -.vscode \ No newline at end of file +.vscode +test/.CondaPkg/ diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 60e020440..32ca34a06 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -26,7 +26,7 @@ export nqubits, stabilizerview, destabilizerview, logicalxview, logicalzview, phases, fastcolumn, fastrow, - bitview, quantumstate, tab, + bitview, quantumstate, tab, rank, BadDataStructure, affectedqubits, #TODO move to QuantumInterface? # GF2 @@ -173,7 +173,7 @@ Tableau(xzs::AbstractMatrix{Bool}) = Tableau(zeros(UInt8, size(xzs,1)), xzs[:,1: Tableau(t::Tableau) = t -function _T_str(a) # TODO this can be optimized by not creating intermediary PauliOperator objects +function _T_str(a::Union{String,SubString{String}}) # TODO this can be optimized by not creating intermediary PauliOperator objects paulis = [_P_str(strip(s.match)) for s in eachmatch(r"[+-]?\h*[i]?\h*[XYZI_]+", a)] Tableau(paulis) end @@ -229,18 +229,18 @@ end Base.firstindex(tab::Tableau) = 1 -Base.lastindex(tab::Tableau) = length(tab.phases) +Base.lastindex(tab::Tableau) = length(tab.phases)::Int Base.lastindex(tab::Tableau, i) = size(tab)[i] -Base.eachindex(tab::Tableau) = Base.OneTo(lastindex(tab.phases)) +Base.eachindex(tab::Tableau) = Base.OneTo(lastindex(tab.phases)::Int) Base.axes(tab::Tableau) = (Base.OneTo(lastindex(tab)), Base.OneTo(nqubits(tab))) Base.axes(tab::Tableau,i) = axes(tab)[i] -Base.size(tab::Tableau) = (length(tab.phases),nqubits(tab)) +Base.size(tab::Tableau) = (length(tab.phases)::Int, nqubits(tab)) Base.size(tab::Tableau,i) = size(tab)[i] -Base.length(tab::Tableau) = length(tab.phases) +Base.length(tab::Tableau) = length(tab.phases)::Int Base.:(==)(l::Tableau, r::Tableau) = r.nqubits==l.nqubits && r.phases==l.phases && r.xzs==l.xzs @@ -378,7 +378,7 @@ macro S_str(a) quote Stabilizer(_T_str($a)) end end Base.getindex(stab::Stabilizer, i::Int) = tab(stab)[i] -Base.getindex(stab::Stabilizer, i) = Stabilizer(tab(stab)[i]) +Base.getindex(stab::Stabilizer, i) = Stabilizer(tab(stab)[i]::Tableau) @inline Base.getindex(stab::Stabilizer, r::Int, c) = tab(stab)[r,c] Base.getindex(stab::Stabilizer, r, c) = Stabilizer(tab(stab)[r,c]) Base.view(stab::Stabilizer, r) = Stabilizer(view(tab(stab),r)) @@ -497,11 +497,11 @@ function MixedStabilizer(s::Stabilizer{T}) where {T} MixedStabilizer(spadded,zr) end -MixedStabilizer(s::Stabilizer,rank::Int) = MixedStabilizer(tab(s),rank) +MixedStabilizer(s::Stabilizer,rank::Int) = MixedStabilizer(tab(s), rank) -Base.length(d::MixedStabilizer) = length(d.tab) +Base.length(d::MixedStabilizer) = length(tab(d)) -Base.copy(ms::MixedStabilizer) = MixedStabilizer(copy(ms.tab),ms.rank) +Base.copy(ms::MixedStabilizer) = MixedStabilizer(copy(tab(ms)), rank(ms)) ############################## # Mixed Destabilizer states @@ -580,7 +580,7 @@ function MixedDestabilizer(stab::Stabilizer{T}; undoperm=true, reportperm=false) end function MixedDestabilizer(d::Destabilizer, r::Int) - l,n = size(d.tab) + l,n = size(tab(d)) if l==2n MixedDestabilizer(tab(d), r) else @@ -588,7 +588,7 @@ function MixedDestabilizer(d::Destabilizer, r::Int) end end function MixedDestabilizer(d::Destabilizer) - l,n = size(d.tab) + l,n = size(tab(d)) if l==2n MixedDestabilizer(d, nqubits(d)) else @@ -599,9 +599,9 @@ end MixedDestabilizer(d::MixedStabilizer) = MixedDestabilizer(stabilizerview(d)) MixedDestabilizer(d::MixedDestabilizer) = d -Base.length(d::MixedDestabilizer) = length(d.tab)÷2 +Base.length(d::MixedDestabilizer) = length(tab(d))÷2 -Base.copy(d::MixedDestabilizer) = MixedDestabilizer(copy(d.tab),d.rank) +Base.copy(d::MixedDestabilizer) = MixedDestabilizer(copy(tab(d)),rank(d)) ############################## # Subtableau views @@ -610,17 +610,17 @@ Base.copy(d::MixedDestabilizer) = MixedDestabilizer(copy(d.tab),d.rank) """A view of the subtableau corresponding to the stabilizer. See also [`tab`](@ref), [`destabilizerview`](@ref), [`logicalxview`](@ref), [`logicalzview`](@ref)""" @inline stabilizerview(s::Stabilizer) = s @inline stabilizerview(s::Destabilizer) = Stabilizer(@view tab(s)[end÷2+1:end]) -@inline stabilizerview(s::MixedStabilizer) = Stabilizer(@view tab(s)[1:s.rank]) -@inline stabilizerview(s::MixedDestabilizer) = Stabilizer(@view tab(s)[end÷2+1:end÷2+s.rank]) +@inline stabilizerview(s::MixedStabilizer) = Stabilizer(@view tab(s)[1:rank(s)]) +@inline stabilizerview(s::MixedDestabilizer) = Stabilizer(@view tab(s)[end÷2+1:end÷2+rank(s)]) """A view of the subtableau corresponding to the destabilizer. See also [`tab`](@ref), [`stabilizerview`](@ref), [`logicalxview`](@ref), [`logicalzview`](@ref)""" @inline destabilizerview(s::Destabilizer) = Stabilizer(@view tab(s)[1:end÷2]) -@inline destabilizerview(s::MixedDestabilizer) = Stabilizer(@view tab(s)[1:s.rank]) +@inline destabilizerview(s::MixedDestabilizer) = Stabilizer(@view tab(s)[1:rank(s)]) """A view of the subtableau corresponding to the logical X operators. See also [`tab`](@ref), [`stabilizerview`](@ref), [`destabilizerview`](@ref), [`logicalzview`](@ref)""" -@inline logicalxview(s::MixedDestabilizer) = Stabilizer(@view tab(s)[s.rank+1:end÷2]) +@inline logicalxview(s::MixedDestabilizer) = Stabilizer(@view tab(s)[rank(s)+1:end÷2]) """A view of the subtableau corresponding to the logical Z operators. See also [`tab`](@ref), [`stabilizerview`](@ref), [`destabilizerview`](@ref), [`logicalxview`](@ref)""" -@inline logicalzview(s::MixedDestabilizer) = Stabilizer(@view tab(s)[end÷2+s.rank+1:end]) +@inline logicalzview(s::MixedDestabilizer) = Stabilizer(@view tab(s)[end÷2+rank(s)+1:end]) """The number of qubits of a given state.""" @inline nqubits(s::AbstractStabilizer) = nqubits(tab(s)) diff --git a/src/dense_cliffords.jl b/src/dense_cliffords.jl index baec10f6e..e4113e0e2 100644 --- a/src/dense_cliffords.jl +++ b/src/dense_cliffords.jl @@ -62,7 +62,7 @@ CliffordOperator(op::CliffordOperator) = op CliffordOperator(paulis::AbstractVector{<:PauliOperator}) = CliffordOperator(Tableau(paulis)) CliffordOperator(destab::Destabilizer) = CliffordOperator(tab(destab)) -Base.:(==)(l::CliffordOperator, r::CliffordOperator) = l.tab == r.tab +Base.:(==)(l::CliffordOperator, r::CliffordOperator) = tab(l) == tab(r) Base.hash(c::T, h::UInt) where {T<:CliffordOperator} = hash(T, hash(tab(c), h)) Base.getindex(c::CliffordOperator, args...) = getindex(tab(c), args...) @@ -85,16 +85,16 @@ digits_subchars = collect("₀₁₂₃₄₅₆₇₈₉") digits_substr(n::Int,nwidth::Int) = join(([digits_subchars[d+1] for d in reverse(digits(n, pad=nwidth))])) function Base.copy(c::CliffordOperator) - CliffordOperator(copy(c.tab)) + CliffordOperator(copy(tab(c))) end -@inline nqubits(c::CliffordOperator) = nqubits(c.tab) +@inline nqubits(c::CliffordOperator) = nqubits(tab(c)) -Base.zero(c::CliffordOperator) = CliffordOperator(zero(c.tab)) +Base.zero(c::CliffordOperator) = CliffordOperator(zero(tab(c))) Base.zero(::Type{<:CliffordOperator}, n) = CliffordOperator(zero(Tableau, 2n, n)) function Base.:(*)(l::AbstractCliffordOperator, r::CliffordOperator) - tab = copy(r.tab) + tab = copy(QuantumClifford.tab(r)) apply!(Stabilizer(tab),l) # TODO maybe not the most elegant way to perform apply!(::Tableau, gate) CliffordOperator(tab) end @@ -106,7 +106,7 @@ end # TODO create Base.permute! and getindex(..., permutation_array) function permute(c::CliffordOperator,p) # TODO this is a slow stupid implementation - CliffordOperator(Tableau([c.tab[i][p] for i in 1:2*nqubits(c)][vcat(p,p.+nqubits(c))])) + CliffordOperator(Tableau([tab(c)[i][p] for i in 1:2*nqubits(c)][vcat(p,p.+nqubits(c))])) end """Nonvectorized version of `apply!` used for unit tests.""" diff --git a/src/ecc/circuits.jl b/src/ecc/circuits.jl index 45aabf1a7..72b3f338c 100644 --- a/src/ecc/circuits.jl +++ b/src/ecc/circuits.jl @@ -74,6 +74,7 @@ function perm_to_transpositions(perm) for i in n:-1:1 if perm[i]!=i j = findfirst(==(i), perm) + @assert !isnothing(j) push!(transpositions, (i, j)) perm[j] = perm[i] end diff --git a/src/ecc/decoder_pipeline.jl b/src/ecc/decoder_pipeline.jl index 3ac2680cf..aeb481851 100644 --- a/src/ecc/decoder_pipeline.jl +++ b/src/ecc/decoder_pipeline.jl @@ -251,7 +251,7 @@ function create_lookup_table(code::Stabilizer) for bit_to_be_flipped in 1:qubits for error_type in [single_x, single_y, single_z] # Generate e⃗ - error = error_type(qubits, bit_to_be_flipped) + error = error_type(qubits, bit_to_be_flipped)::PauliOperator{Array{UInt8, 0}, Vector{UInt}} # Calculate s⃗ # (check which stabilizer rows do not commute with the Pauli error) syndrome = comm(error, code) diff --git a/src/fastmemlayout.jl b/src/fastmemlayout.jl index f7019cd1a..67e2bbbd7 100644 --- a/src/fastmemlayout.jl +++ b/src/fastmemlayout.jl @@ -20,14 +20,14 @@ fastrow(t::Tableau{Tₚᵥ,Tₘ}) where {Tₚᵥ, Tₘ<:Adjoint} = Tableau(t.pha fastcolumn(t::Tableau{Tₚᵥ,Tₘ}) where {Tₚᵥ, Tₘ} = Tableau(t.phases, t.nqubits, collect(t.xzs')') fastcolumn(t::Tableau{Tₚᵥ,Tₘ}) where {Tₚᵥ, Tₘ<:Adjoint} = t -fastrow(s::Stabilizer) = Stabilizer(fastrow(s.tab)) -fastcolumn(s::Stabilizer) = Stabilizer(fastcolumn(s.tab)) +fastrow(s::Stabilizer) = Stabilizer(fastrow(tab(s))) +fastcolumn(s::Stabilizer) = Stabilizer(fastcolumn(tab(s))) -fastrow(s::Destabilizer) = Destabilizer(fastrow(s.tab)) -fastcolumn(s::Destabilizer) = Destabilizer(fastcolumn(s.tab)) +fastrow(s::Destabilizer) = Destabilizer(fastrow(tab(s))) +fastcolumn(s::Destabilizer) = Destabilizer(fastcolumn(tab(s))) -fastrow(s::MixedStabilizer) = MixedStabilizer(fastrow(s.tab), s.rank) -fastcolumn(s::MixedStabilizer) = MixedStabilizer(fastcolumn(s.tab), s.rank) +fastrow(s::MixedStabilizer) = MixedStabilizer(fastrow(tab(s)), rank(s)) +fastcolumn(s::MixedStabilizer) = MixedStabilizer(fastcolumn(tab(s)), rank(s)) -fastrow(s::MixedDestabilizer) = MixedDestabilizer(fastrow(s.tab), s.rank) -fastcolumn(s::MixedDestabilizer) = MixedDestabilizer(fastcolumn(s.tab), s.rank) +fastrow(s::MixedDestabilizer) = MixedDestabilizer(fastrow(tab(s)), rank(s)) +fastcolumn(s::MixedDestabilizer) = MixedDestabilizer(fastcolumn(tab(s)), rank(s)) diff --git a/src/linalg.jl b/src/linalg.jl index b95fba888..ad252e95f 100644 --- a/src/linalg.jl +++ b/src/linalg.jl @@ -260,7 +260,7 @@ function tensor(ops::CliffordOperator...) # TODO implement \otimes for Destabili last_zrow = ntot last_xrow = 0 for op in ops - t = op.tab + t = QuantumClifford.tab(op) _, last_zrow, _ = puttableau!(tab, (@view t[end÷2+1:end]), last_zrow, last_xrow) _, last_xrow, _ = puttableau!(tab, (@view t[1:end÷2]), last_xrow, last_xrow) end diff --git a/src/mul_leftright.jl b/src/mul_leftright.jl index 122fb1e61..9803ed2b5 100644 --- a/src/mul_leftright.jl +++ b/src/mul_leftright.jl @@ -56,12 +56,6 @@ function mul_ordered_lv!(r::AbstractVector{T}, l::AbstractVector{T}; phases::Val end =# -function mul_ordered!(r::SubArray{T,1,P,I1,L1}, l::SubArray{T,1,P,I2,L2}; phases::Val{B}=Val(true)) where {T<:Unsigned, B, I1, I2, L1, L2, P<:Adjoint} - # This method exists because SIMD.jl does not play well with Adjoint - # Delete it and try `QuantumClifford.mul_left!(fastcolumn(random_stabilizer(194)), 2, 1)` # works fine for 192 - _mul_ordered_nonvec!(r,l; phases=B) -end - function mul_ordered!(r::SubArray{T,1,P,I2,L2}, l::AbstractVector{T}; phases::Val{B}=Val(true)) where {T<:Unsigned, B, I2, L2, P<:Adjoint} # This method exists because SIMD.jl does not play well with Adjoint _mul_ordered_nonvec!(r,l; phases=B) diff --git a/src/pauli_operator.jl b/src/pauli_operator.jl index e556628bc..a843ef10b 100644 --- a/src/pauli_operator.jl +++ b/src/pauli_operator.jl @@ -77,7 +77,7 @@ function zbit(p::PauliOperator) [(word>>s)&one==one for word in zview(p) for s in 0:size-1][begin:p.nqubits] end -function _P_str(a) +function _P_str(a::Union{String,SubString{String}}) letters = filter(x->occursin(x,"_IZXY"),a) phase = phasedict[strip(filter(x->!occursin(x,"_IZXY"),a))] PauliOperator(phase, [l=='X'||l=='Y' for l in letters], [l=='Z'||l=='Y' for l in letters]) @@ -87,7 +87,7 @@ macro P_str(a) quote _P_str($a) end end -Base.getindex(p::PauliOperator{Tₚ,Tᵥ}, i::Int) where {Tₚ, Tᵥₑ<:Unsigned, Tᵥ<:AbstractVector{Tᵥₑ}} = (p.xz[_div(Tᵥₑ, i-1)+1] & Tᵥₑ(0x1)<<_mod(Tᵥₑ,i-1))!=0x0, (p.xz[end÷2+_div(Tᵥₑ,i-1)+1] & Tᵥₑ(0x1)<<_mod(Tᵥₑ,i-1))!=0x0 +Base.getindex(p::PauliOperator{Tₚ,Tᵥ}, i::Int) where {Tₚ, Tᵥₑ<:Unsigned, Tᵥ<:AbstractVector{Tᵥₑ}} = ((p.xz[_div(Tᵥₑ, i-1)+1] & Tᵥₑ(0x1)<<_mod(Tᵥₑ,i-1))!=0x0)::Bool, ((p.xz[end÷2+_div(Tᵥₑ,i-1)+1] & Tᵥₑ(0x1)<<_mod(Tᵥₑ,i-1))!=0x0)::Bool Base.getindex(p::PauliOperator{Tₚ,Tᵥ}, r) where {Tₚ, Tᵥₑ<:Unsigned, Tᵥ<:AbstractVector{Tᵥₑ}} = PauliOperator(p.phase[], xbit(p)[r], zbit(p)[r]) function Base.setindex!(p::PauliOperator{Tₚ,Tᵥ}, (x,z)::Tuple{Bool,Bool}, i) where {Tₚ, Tᵥₑ, Tᵥ<:AbstractVector{Tᵥₑ}} @@ -176,7 +176,8 @@ end function embed(n::Int, indices, p::PauliOperator) if nqubits(p) == length(indices) pout = zero(typeof(p), n) - @inbounds @simd for i in eachindex(indices) + @inbounds @simd for i_ in eachindex(indices) + i = i_::Int pout[indices[i]] = p[i] end pout.phase[] = p.phase[] diff --git a/src/project_trace_reset.jl b/src/project_trace_reset.jl index 19e49521b..4b2e6e2e5 100644 --- a/src/project_trace_reset.jl +++ b/src/project_trace_reset.jl @@ -339,7 +339,7 @@ end function _project!(d::Destabilizer,pauli::PauliOperator;keep_result::Val{Bkr}=Val(true),phases::Val{Bp}=Val(true)) where {Bkr, Bp} # repetition between Destabilizer and MixedDestabilizer, but the redundancy makes the two codes slightly simpler and easier to infer anticommutes = 0 - tab = d.tab + tab = QuantumClifford.tab(d) stabilizer = stabilizerview(d) destabilizer = destabilizerview(d) r = trusted_rank(d) @@ -377,7 +377,7 @@ end function _project!(d::MixedDestabilizer,pauli::PauliOperator;keep_result::Val{Bkr}=Val(true),phases::Val{Bp}=Val(true)) where {Bkr, Bp} # repetition between Destabilizer and MixedDestabilizer, but the redundancy makes the two codes slightly simpler and easier to infer anticommutes = 0 - tab = d.tab + tab = QuantumClifford.tab(d) stabilizer = stabilizerview(d) destabilizer = destabilizerview(d) r = trusted_rank(d) @@ -497,7 +497,7 @@ end """Internal method used to implement [`projectX!`](@ref), [`projectZ!`](@ref), and [`projectY!`](@ref).""" function project_cond!(d::MixedDestabilizer,qubit::Int,cond::Val{IS},reset::Val{RESET};keep_result::Bool=true,phases::Val{PHASES}=Val(true)) where {IS,RESET,PHASES} anticommutes = 0 - tab = d.tab + tab = QuantumClifford.tab(d) stabilizer = stabilizerview(d) destabilizer = destabilizerview(d) r = d.rank @@ -647,7 +647,7 @@ function traceout!(s::Union{MixedStabilizer, MixedDestabilizer}, qubits; phases= if rank return (s, i) else return s end end -function _expand_pauli(pauli,qubits,n) # TODO rename and make public +function _expand_pauli(pauli::PauliOperator,qubits,n) # TODO rename and make public expanded = zero(PauliOperator,n) for (ii, i) in enumerate(qubits) expanded[i] = pauli[ii] @@ -886,4 +886,4 @@ See also: [`traceout!`](@ref) """ function delete_columns(𝒮::Stabilizer, subset) return 𝒮[:, setdiff(1:nqubits(𝒮), subset)] -end \ No newline at end of file +end diff --git a/src/randoms.jl b/src/randoms.jl index 1d4fff388..c87932fd4 100644 --- a/src/randoms.jl +++ b/src/randoms.jl @@ -184,7 +184,7 @@ function nemo_inv(a, n)::Matrix{UInt8} end """Sample (h, S) from the distribution P_n(h, S) from Bravyi and Maslov Algorithm 1.""" -function quantum_mallows(rng, n) # each one is benchmakred in benchmarks/quantum_mallows.jl +function quantum_mallows(rng::AbstractRNG, n::Int) # each one is benchmarked in benchmarks/quantum_mallows.jl arr = collect(1:n) hadamard = falses(n) perm = zeros(Int64, n) @@ -202,7 +202,7 @@ end """ This function samples a number from 1 to `n` where `n >= 1` probability of outputting `i` is proportional to `2^i`""" -function sample_geometric_2(rng, n::Integer) +function sample_geometric_2(rng::AbstractRNG, n::Integer) n < 1 && throw(DomainError(n)) if n<30 k = rand(rng, 2:UInt(2)^n) @@ -217,7 +217,7 @@ function sample_geometric_2(rng, n::Integer) end """Assign (symmetric) random ints to off diagonals of matrix.""" -function fill_tril(rng, matrix, n; symmetric::Bool=false) +function fill_tril(rng::AbstractRNG, matrix, n; symmetric::Bool=false) # Add (symmetric) random ints to off diagonals @inbounds for row in 1:n, col in 1:row-1 b = rand(rng, Bool) diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index e73622217..601696855 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -203,7 +203,7 @@ SingleQubitOperator(p::sInvSQRTY) = SingleQubitOperator(p.q, false, tr SingleQubitOperator(o::SingleQubitOperator) = o function SingleQubitOperator(op::CliffordOperator, qubit) nqubits(op)==1 || throw(DimensionMismatch("You are trying to convert a multiqubit `CliffordOperator` into a symbolic `SingleQubitOperator`.")) - SingleQubitOperator(qubit,op.tab[1,1]...,op.tab[2,1]...,(~).(iszero.(op.tab.phases))...) + SingleQubitOperator(qubit,tab(op)[1,1]...,tab(op)[2,1]...,(~).(iszero.(tab(op).phases))...) end SingleQubitOperator(op::CliffordOperator) = SingleQubitOperator(op, 1) diff --git a/src/tableau_show.jl b/src/tableau_show.jl index 52e1adc07..303549573 100644 --- a/src/tableau_show.jl +++ b/src/tableau_show.jl @@ -76,15 +76,15 @@ function Base.show(io::IO, d::Destabilizer) print(io, "Destablizer $r×$q") elseif get(io, :limit, false) h,w = displaysize(io) - println(io, "𝒟ℯ𝓈𝓉𝒶𝒷" * "━"^max(min(w-9,size(d.tab,2)-4),0)) - _show(io, destabilizerview(d).tab, w, h÷2) - println(io, "\n𝒮𝓉𝒶𝒷" * "━"^max(min(w-7,size(d.tab,2)-2),0)) - _show(io, stabilizerview(d).tab, w, h÷2) + println(io, "𝒟ℯ𝓈𝓉𝒶𝒷" * "━"^max(min(w-9,size(tab(d),2)-4),0)) + _show(io, tab(destabilizerview(d)), w, h÷2) + println(io, "\n𝒮𝓉𝒶𝒷" * "━"^max(min(w-7,size(tab(d),2)-2),0)) + _show(io, tab(stabilizerview(d)), w, h÷2) else - println(io, "𝒟ℯ𝓈𝓉𝒶𝒷" * "━"^max(size(d.tab,2)-4,0)) - _show(io, destabilizerview(d).tab, missing, missing) - println(io, "\n𝒮𝓉𝒶𝒷" * "━"^max(size(d.tab,2)-2,0)) - _show(io, stabilizerview(d).tab, missing, missing) + println(io, "𝒟ℯ𝓈𝓉𝒶𝒷" * "━"^max(size(tab(d),2)-4,0)) + _show(io, tab(destabilizerview(d)), missing, missing) + println(io, "\n𝒮𝓉𝒶𝒷" * "━"^max(size(tab(d),2)-2,0)) + _show(io, tab(stabilizerview(d)), missing, missing) end end @@ -95,36 +95,36 @@ function Base.show(io::IO, d::MixedDestabilizer) print(io, "MixedDestablizer $r×$q") elseif get(io, :limit, false) h,w = displaysize(io) - println(io, "𝒟ℯ𝓈𝓉𝒶𝒷" * "━"^max(min(w-9,size(d.tab,2)-4),0)) - _show(io, destabilizerview(d).tab, w, h÷4) + println(io, "𝒟ℯ𝓈𝓉𝒶𝒷" * "━"^max(min(w-9,size(tab(d),2)-4),0)) + _show(io, tab(destabilizerview(d)), w, h÷4) if r != q println(io) - println(io, "𝒳ₗ" * "━"^max(min(w-5,size(d.tab,2)),0)) - _show(io, logicalxview(d).tab, w, h÷4) + println(io, "𝒳ₗ" * "━"^max(min(w-5,size(tab(d),2)),0)) + _show(io, tab(logicalxview(d)), w, h÷4) end println(io) - println(io, "𝒮𝓉𝒶𝒷" * "━"^max(min(w-7,size(d.tab,2)-2),0)) - _show(io, stabilizerview(d).tab, w, h÷4) + println(io, "𝒮𝓉𝒶𝒷" * "━"^max(min(w-7,size(tab(d),2)-2),0)) + _show(io, tab(stabilizerview(d)), w, h÷4) if r != q println(io) - println(io, "𝒵ₗ" * "━"^max(min(w-5,size(d.tab,2)),0)) - _show(io, logicalzview(d).tab, w, h÷4) + println(io, "𝒵ₗ" * "━"^max(min(w-5,size(tab(d),2)),0)) + _show(io, tab(logicalzview(d)), w, h÷4) end else - println(io, "𝒟ℯ𝓈𝓉𝒶𝒷" * "━"^max(size(d.tab,2)-4,0)) - _show(io, destabilizerview(d).tab, missing, missing) + println(io, "𝒟ℯ𝓈𝓉𝒶𝒷" * "━"^max(size(tab(d),2)-4,0)) + _show(io, tab(destabilizerview(d)), missing, missing) if r != q println(io) - println(io, "𝒳ₗ" * "━"^max(size(d.tab,2),0)) - _show(io, logicalxview(d).tab, missing, missing) + println(io, "𝒳ₗ" * "━"^max(size(tab(d),2),0)) + _show(io, tab(logicalxview(d)), missing, missing) end println(io) - println(io, "𝒮𝓉𝒶𝒷" * "━"^max(size(d.tab,2)-2,0)) - _show(io, stabilizerview(d).tab, missing, missing) + println(io, "𝒮𝓉𝒶𝒷" * "━"^max(size(tab(d),2)-2,0)) + _show(io, tab(stabilizerview(d)), missing, missing) if r != q println(io) - println(io, "𝒵ₗ" * "━"^max(size(d.tab,2)),0) - _show(io, logicalzview(d).tab, missing, missing) + println(io, "𝒵ₗ" * "━"^max(size(tab(d),2)),0) + _show(io, tab(logicalzview(d)), missing, missing) end end end @@ -144,7 +144,7 @@ function _show(io::IO, c::CliffordOperator, limit=50, limit_vertical=20) continue end print(io, "X"*digits_substr(i,nwidth)*" ⟼ ") - _show(io, c.tab[i], _limit) + _show(io, tab(c)[i], _limit) println(io) end for i in range @@ -153,7 +153,7 @@ function _show(io::IO, c::CliffordOperator, limit=50, limit_vertical=20) continue end print(io, "Z"*digits_substr(i,nwidth)*" ⟼ ") - _show(io, c.tab[i+n], _limit) + _show(io, tab(c)[i+n], _limit) i!=n && println(io) end end diff --git a/test/Project.toml b/test/Project.toml index cd15ae310..eb254c893 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -25,6 +25,7 @@ RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" SIMD = "fdea26ae-647d-5447-a871-4b548cad5224" StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" Static = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StridedViews = "4db3bf67-4bd7-4b4e-b153-31dc3fb37143" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" diff --git a/test/test_jet.jl b/test/test_jet.jl index 91348c168..7f0248ab7 100644 --- a/test/test_jet.jl +++ b/test/test_jet.jl @@ -9,6 +9,7 @@ using Nemo using AbstractAlgebra using Hecke + using StaticArrays rep = report_package("QuantumClifford"; ignored_modules=( @@ -21,10 +22,10 @@ AnyFrameModule(Nemo), AnyFrameModule(AbstractAlgebra), AnyFrameModule(Hecke), + AnyFrameModule(StaticArrays), )) @show rep @show length(JET.get_reports(rep)) - @test_broken length(JET.get_reports(rep)) == 0 - @test length(JET.get_reports(rep)) <= 23 + @test length(JET.get_reports(rep)) == 0 end diff --git a/test/test_noisycircuits.jl b/test/test_noisycircuits.jl index 8ae674161..4f5e92c3e 100644 --- a/test/test_noisycircuits.jl +++ b/test/test_noisycircuits.jl @@ -228,7 +228,7 @@ state = Register(MixedDestabilizer(S"ZZ"), zeros(Bool, 1)) meas = PauliMeasurement(P"ZI", 1) state, flag = applywstatus!(state, meas) - @test state.stab.rank == 2 + @test rank(state.stab) == 2 tab(state.stab).phases .= 0 @test stabilizerview(state.stab) == S"ZZ ZI" diff --git a/test/test_projections.jl b/test/test_projections.jl index 03d56e1e2..beae0ab70 100644 --- a/test/test_projections.jl +++ b/test/test_projections.jl @@ -76,12 +76,12 @@ @test_throws BadDataStructure pds, a, r = project!(copy(ds),p) pms, a, r = project!(copy(ms),p) @test mixed_stab_looks_good(pms) - @test pms.rank==3 - @test a==pms.rank && isnothing(r) + @test rank(pms)==3 + @test a==rank(pms) && isnothing(r) pmds, a, r = project!(copy(mds),p) @test mixed_destab_looks_good(pmds) - @test pmds.rank==3 - @test a==pmds.rank && isnothing(r) + @test rank(pmds)==3 + @test a==rank(pmds) && isnothing(r) p = P"ZZI" ps, a, r = project!(copy(s),p) @@ -90,11 +90,11 @@ @test_throws BadDataStructure pds, a, r = project!(copy(ds),p) pms, a, r = project!(copy(ms),p) @test mixed_stab_looks_good(pms) - @test pms.rank==2 + @test rank(pms)==2 @test a==0 && r==0x2 pmds, a, r = project!(copy(mds),p) @test mixed_destab_looks_good(pmds) - @test pmds.rank==2 + @test rank(pmds)==2 @test a==0 && r==0x2 @test canonicalize!(ps)==canonicalize!(stabilizerview(pms))==canonicalize!(stabilizerview(pmds)) @@ -107,11 +107,11 @@ @test a==2 && isnothing(r) pms, a, r = project!(copy(ms),p) @test mixed_stab_looks_good(pms) - @test pms.rank==2 + @test rank(pms)==2 @test a==2 && isnothing(r) pmds, a, r = project!(copy(mds),p) @test mixed_destab_looks_good(pmds) - @test pmds.rank==2 + @test rank(pmds)==2 @test a==2 && isnothing(r) @test canonicalize!(ps)==canonicalize!(stabilizerview(pms))==canonicalize!(stabilizerview(pds))==canonicalize!(stabilizerview(pmds)) end @@ -162,10 +162,10 @@ s = MixedStabilizer(s, 2) ms, a, r = project!(copy(s), P"IZI") @test (a, r) == (0, 0x0) # on commuting operator in the stabilizer - @test ms.rank == 2 + @test rank(ms) == 2 ms, a, r = project!(copy(s), P"IIZ") @test (a, r) == (3, nothing) # on commuting operator out of the stabilizer - @test ms.rank == 3 + @test rank(ms) == 3 s = S"ZII IZI" s = Destabilizer(s) @test_throws BadDataStructure project!(copy(s), P"IZI"; keep_result=true) # on comm @@ -182,16 +182,16 @@ s = MixedDestabilizer(s) mds, a, r = project!(copy(s), P"IZI"; keep_result=true) @test (a, r) == (0, 0x0) # on commuting operator in the stabilizer - @test mds.rank == 2 + @test rank(mds) == 2 mds, a, r = project!(copy(s), P"IIZ"; keep_result=true) @test (a, r) == (3, nothing) # on commuting operator out of the stabilizer - @test mds.rank == 3 + @test rank(mds) == 3 mds, a, r = project!(copy(s), P"IZI"; keep_result=false) @test (a, r) == (0, nothing) # on commuting operator in the stabilizer - @test mds.rank == 2 + @test rank(mds) == 2 mds, a, r = project!(copy(s), P"IIZ"; keep_result=false) @test (a, r) == (3, nothing) # on commuting operator out of the stabilizer - @test mds.rank == 3 + @test rank(mds) == 3 end @testset "Results from canonicalization vs from destabilizer" begin @test generate!(P"_Z", S"XZ") === nothing # for bug fixed in 4b536231c3ee4e6446262fcc61ba8da669415bc8 @@ -206,7 +206,7 @@ _, ams, rms = project!(ms,p) _, amd, rmd = project!(md,p) @test rs == rms == rmd - @test (md.rank!=r) || (canonicalize!(s) == canonicalize!(stabilizerview(ms))) + @test (rank(md)!=r) || (canonicalize!(s) == canonicalize!(stabilizerview(ms))) @test canonicalize!(stabilizerview(ms)) == canonicalize!(stabilizerview(md)) if as == 0 @test ams == amd == 0 diff --git a/test/test_trace.jl b/test/test_trace.jl index 424399ce1..6ed1022e2 100644 --- a/test/test_trace.jl +++ b/test/test_trace.jl @@ -101,7 +101,7 @@ @test canonicalize!(copy(ssr2v)[:,perm])[1:z] == canonicalize!(copy(newstate)) @test canonicalize!(msr2v) == c[1:z] # Compare different datastractures - @test canonicalize!(copy(stabilizerview(mdr1)))==canonicalize!(copy(stabilizerview(msr1)))==canonicalize!(ssr1[1:mdr1.rank]) + @test canonicalize!(copy(stabilizerview(mdr1)))==canonicalize!(copy(stabilizerview(msr1)))==canonicalize!(ssr1[1:rank(mdr1)]) end end end From 66c05843abb783f61eb2f887f728274852c484dc Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Sun, 20 Oct 2024 12:58:25 -0400 Subject: [PATCH 44/66] .gitignore for CondaPkg --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a61e78b68..b159e1a46 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ scratch/ *.cov .vscode test/.CondaPkg/ +docs/.CondaPkg/ From 6a8dbb426da67c05c3dc9f74bb67941307d938c3 Mon Sep 17 00:00:00 2001 From: Tommy Hofmann Date: Tue, 22 Oct 2024 04:30:19 +0200 Subject: [PATCH 45/66] ci: tweak ci.yml (#401) --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e00b68835..1f7eba665 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,14 @@ on: branches: [master, main] tags: ["*"] pull_request: + +concurrency: + # group by workflow and ref; the last slightly strange component ensures that for pull + # requests, we limit to 1 concurrent job, but for the master branch we don't + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/master' || github.run_number }} + # Cancel intermediate builds, but only if it is a pull request build. + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + env: PYTHON: ~ jobs: From 73ccb34553070bbf5f31c589aa744352929ff1a0 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 24 Oct 2024 19:57:10 -0400 Subject: [PATCH 46/66] remove unnecessary imports --- src/grouptableaux.jl | 33 +++++++++++++++------------------ test/test_group_tableaux.jl | 17 ++++++++--------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/grouptableaux.jl b/src/grouptableaux.jl index b0f76e1c2..208f1ddfb 100644 --- a/src/grouptableaux.jl +++ b/src/grouptableaux.jl @@ -1,6 +1,3 @@ -using Graphs -using LinearAlgebra - """ Return the full stabilizer group represented by the input generating set (a [`Stabilizer`](@ref)). @@ -15,8 +12,8 @@ julia> groupify(S"XZ ZX") ``` """ function groupify(s::Stabilizer) - # Create a `Tableau` of 2ⁿ n-qubit identity Pauli operators(where n is the size of - # `Stabilizer` s), then multiply each one by a different subset of the elements in s to + # Create a `Tableau` of 2ⁿ n-qubit identity Pauli operators(where n is the size of + # `Stabilizer` s), then multiply each one by a different subset of the elements in s to # create all 2ⁿ unique elements in the group generated by s, then return the `Tableau`. n = length(s)::Int group = zero(Tableau, 2^n, nqubits(s)) @@ -27,7 +24,7 @@ function groupify(s::Stabilizer) end end end - return group + return group end @@ -44,8 +41,8 @@ julia> minimal_generating_set(S"__ XZ ZX YY") ``` """ function minimal_generating_set(s::Stabilizer) - # Canonicalize `Stabilizer` s, then return a `Stabilizer` with all non-identity Pauli operators - # in the result. If s consists of only identity operators, return the negative + # Canonicalize `Stabilizer` s, then return a `Stabilizer` with all non-identity Pauli operators + # in the result. If s consists of only identity operators, return the negative # identity operator if one is contained in s, and the positive identity operator otherwise. s, _, r = canonicalize!(copy(s), ranks=true) if r == 0 @@ -60,7 +57,7 @@ function minimal_generating_set(s::Stabilizer) end """ -Return the full Pauli group of a given length. Phases are ignored by default, +Return the full Pauli group of a given length. Phases are ignored by default, but can be included by setting `phases=true`. ```jldoctest @@ -131,8 +128,8 @@ julia> normalizer(T"X") ``` """ function normalizer(t::Tableau) - # For each `PauliOperator` p in the with same number of qubits as the `Stabilizer` s, iterate through s and check each - # operator's commutivity with p. If they all commute, add p a vector of `PauliOperators`. Return the vector + # For each `PauliOperator` p in the with same number of qubits as the `Stabilizer` s, iterate through s and check each + # operator's commutivity with p. If they all commute, add p a vector of `PauliOperators`. Return the vector # converted to `Tableau`. n = nqubits(t) pgroup = pauligroup(n, phases=false) @@ -161,7 +158,7 @@ julia> centralizer(T"XX ZZ _Z") + ZZ ``` """ -function centralizer(t::Tableau) +function centralizer(t::Tableau) center = typeof(t[1])[] for P in t commutes = 0 @@ -175,7 +172,7 @@ function centralizer(t::Tableau) push!(center, P) end end - if length(center) == 0 + if length(center) == 0 return Tableau(zeros(Bool, 1,1)) end c = Tableau(center) @@ -183,7 +180,7 @@ function centralizer(t::Tableau) end """ -Return the subset of Paulis in a Stabilizer that have identity operators on all qubits corresponding to +Return the subset of Paulis in a Stabilizer that have identity operators on all qubits corresponding to the given subset, without the entries corresponding to subset. ```jldoctest @@ -196,9 +193,9 @@ function contractor(s::Stabilizer, subset) for p in s contractable = true for i in subset - if p[i] != (false, false) - contractable = false - break + if p[i] != (false, false) + contractable = false + break end end if contractable push!(result, p[setdiff(1:length(p), subset)]) end @@ -208,4 +205,4 @@ function contractor(s::Stabilizer, subset) else return Tableau(zeros(Bool, 1,1)) end -end \ No newline at end of file +end diff --git a/test/test_group_tableaux.jl b/test/test_group_tableaux.jl index afec47e90..d208afdff 100644 --- a/test/test_group_tableaux.jl +++ b/test/test_group_tableaux.jl @@ -1,11 +1,10 @@ -@testitem "Classical" begin +@testitem "group theory routines" begin using Test - using Random using QuantumClifford # Including sizes that would test off-by-one errors in the bit encoding. - test_sizes = [1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17] + test_sizes = [1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17] # Zero function(in groupify) slows down around 2^30(n=30),eventually breaks small_test_sizes = [1, 2, 3, 4, 5, 7] # Pauligroup slows around n = 8 @@ -45,7 +44,7 @@ end end #Test pauligroup - for n in [1, small_test_sizes...] + for n in [1, small_test_sizes...] @test length(QuantumClifford.pauligroup(n, phases=false)) == 4^n @test length(QuantumClifford.pauligroup(n, phases=true)) == 4^(n+1) end @@ -98,7 +97,7 @@ end c = contractor(s, subset) count = 0 - for stabilizer in s + for stabilizer in s contractable = true for i in subset if stabilizer[i] != (false, false) contractable = false end @@ -111,9 +110,9 @@ p = zero(PauliOperator, nqubits(s)) index = 0 for i in 1:nqubits(s) - if !(i in subset) - index+=1 - p[i] = contracted[index] + if !(i in subset) + index+=1 + p[i] = contracted[index] end end @test p in s || -1* p in s || 1im * p in s || -1im * p in s @@ -123,4 +122,4 @@ end end end -end \ No newline at end of file +end From 617843c81a4362d1ef02997311dd771a196657c2 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 24 Oct 2024 20:58:55 -0400 Subject: [PATCH 47/66] more tests for sZCrY --- test/test_symcontrolled.jl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/test_symcontrolled.jl b/test/test_symcontrolled.jl index 256f62be3..4fbd86a87 100644 --- a/test/test_symcontrolled.jl +++ b/test/test_symcontrolled.jl @@ -125,4 +125,21 @@ end end end + + @testset "Ket-based definition for Y and rY" begin + for control in (:Z,) + target = :Y + for r in (true, false) + s = Stabilizer(QuantumClifford._T_str(string(control))) + k1 = Ket(s) + s.tab.phases[1] = 0x2 + k2 = Ket(s) + i = Operator(tId1) + o = Operator(CliffordOperator(eval(Symbol(:s,target,))(1),1)) + gate = projector(k1)⊗i + (r ? -1 : -im) * projector(k2)⊗o # XXX there is a -1 here because global phase is arbitrary and thus our Operator(tY) is NOT im*Operator(X)*Operator(Z) + implemented_gate = Operator(CliffordOperator(eval(Symbol(:s,control,:C,(r ? :rY : :Y)))(1,2),2)) + @test gate≈implemented_gate + end + end + end end From 0849b1c9f2d320ce3306961e15642da7dca83a6c Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Fri, 25 Oct 2024 07:45:38 +0500 Subject: [PATCH 48/66] adding all missing types of two qubit SWAP gates and sSQRTZZ/sInvSQRTZZ gate (#336) --------- Co-authored-by: Stefan Krastanov Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 5 ++++ src/QuantumClifford.jl | 3 ++- src/symbolic_cliffords.jl | 49 ++++++++++++++++++++++++++++----------- test/test_symcliff.jl | 13 +++++++++++ 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e260d5c02..06856e122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ # News +## v0.9.13 - dev + +- Implementing additional named two-qubit gates: `sSWAPCX, sInvSWAPCX, sCZSWAP, sCXSWAP, sISWAP, sInvISWAP, + sSQRTZZ, sInvSQRTZZ` + ## v0.9.12 - 2024-10-18 - Minor compat fixes for julia 1.11 in the handling of `hgp` diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 32ca34a06..6837483cb 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -51,7 +51,8 @@ export sHadamardXY, sHadamardYZ, sSQRTX, sInvSQRTX, sSQRTY, sInvSQRTY, sCXYZ, sCZYX, sCNOT, sCPHASE, sSWAP, sXCX, sXCY, sXCZ, sYCX, sYCY, sYCZ, sZCX, sZCY, sZCZ, - sZCrY, sInvZCrY, + sZCrY, sInvZCrY, sSWAPCX, sInvSWAPCX, sCZSWAP, sCXSWAP, sISWAP, sInvISWAP, + sSQRTZZ, sInvSQRTZZ, # Misc Ops SparseGate, sMX, sMY, sMZ, PauliMeasurement, Reset, sMRX, sMRY, sMRZ, diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index 601696855..98f0e5d61 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -308,6 +308,16 @@ macro qubitop2(name, kernel) end # x1 z1 x2 z2 @qubitop2 SWAP (x2 , z2 , x1 , z1 , false) + +@qubitop2 SWAPCX (x2 , z2⊻z1 , x2⊻x1 , z1 , ~iszero((x1 & z1 & x2 & z2) | (~x1 & z1 & x2 & ~z2))) +@qubitop2 InvSWAPCX (x2⊻x1 , z2 , x1 , z2⊻z1 , ~iszero((x1 & z1 & x2 & z2) | ( x1 &~z1 &~x2 & z2))) + +@qubitop2 ISWAP (x2 , x1⊻z2⊻x2 , x1 , x1⊻x2⊻z1 , ~iszero((x1 & z1 & ~x2) | (~x1 & x2 & z2))) +@qubitop2 InvISWAP (x2 , x1⊻z2⊻x2 , x1 , x1⊻x2⊻z1 , ~iszero((x1 &~z1 & ~x2) | (~x1 & x2 &~z2))) + +@qubitop2 CZSWAP (x2 , z2⊻x1 , x1 , x2⊻z1 , ~iszero((x1 & ~z1 & x2 & z2) | (x1 & z1 & x2 & ~z2))) +@qubitop2 CXSWAP (x2⊻x1 , z2 , x1 , z2⊻z1 , ~iszero((x1 & ~z1 &~x2 & z2) | (x1 & z1 & x2 & z2))) + @qubitop2 CNOT (x1 , z1⊻z2 , x2⊻x1 , z2 , ~iszero( (x1 & z1 & x2 & z2) | (x1 & z2 &~(z1|x2)) )) @qubitop2 CPHASE (x1 , z1⊻x2 , x2 , z2⊻x1 , ~iszero( (x1 & z1 & x2 &~z2) | (x1 &~z1 & x2 & z2) )) @@ -326,6 +336,9 @@ end @qubitop2 ZCrY (x1, x1⊻z1⊻x2⊻z2, x1⊻x2, x1⊻z2, ~iszero((x1 &~z1 & x2) | (x1 & ~z1 & ~z2) | (x1 & x2 & ~z2))) @qubitop2 InvZCrY (x1, x1⊻z1⊻x2⊻z2, x1⊻x2, x1⊻z2, ~iszero((x1 & z1 &~x2) | (x1 & z1 & z2) | (x1 &~x2 & z2))) +@qubitop2 SQRTZZ (x1 , x1⊻x2⊻z1 , x2 , x1⊻z2⊻x2 , ~iszero((x1 & z1 & ~x2) | (~x1 & x2 & z2))) +@qubitop2 InvSQRTZZ (x1 , x1⊻x2⊻z1 , x2 , x1⊻z2⊻x2 , ~iszero((x1 &~z1 & ~x2) | (~x1 & x2 &~z2))) + #= To get the boolean formulas for the phase, it is easiest to first write down the truth table for the phase: for i in 0:15 @@ -370,20 +383,28 @@ function Base.show(io::IO, op::AbstractTwoQubitOperator) end end -LinearAlgebra.inv(op::sSWAP) = sSWAP(op.q1, op.q2) -LinearAlgebra.inv(op::sCNOT) = sCNOT(op.q1, op.q2) -LinearAlgebra.inv(op::sCPHASE) = sCPHASE(op.q1, op.q2) -LinearAlgebra.inv(op::sZCX) = sZCX(op.q1, op.q2) -LinearAlgebra.inv(op::sZCY) = sZCY(op.q1, op.q2) -LinearAlgebra.inv(op::sZCZ) = sZCZ(op.q1, op.q2) -LinearAlgebra.inv(op::sXCX) = sXCX(op.q1, op.q2) -LinearAlgebra.inv(op::sXCY) = sXCY(op.q1, op.q2) -LinearAlgebra.inv(op::sXCZ) = sXCZ(op.q1, op.q2) -LinearAlgebra.inv(op::sYCX) = sYCX(op.q1, op.q2) -LinearAlgebra.inv(op::sYCY) = sYCY(op.q1, op.q2) -LinearAlgebra.inv(op::sYCZ) = sYCZ(op.q1, op.q2) -LinearAlgebra.inv(op::sZCrY) = sInvZCrY(op.q1, op.q2) -LinearAlgebra.inv(op::sInvZCrY) = sZCrY(op.q1, op.q2) +LinearAlgebra.inv(op::sSWAP) = sSWAP(op.q1, op.q2) +LinearAlgebra.inv(op::sCNOT) = sCNOT(op.q1, op.q2) +LinearAlgebra.inv(op::sCPHASE) = sCPHASE(op.q1, op.q2) +LinearAlgebra.inv(op::sZCX) = sZCX(op.q1, op.q2) +LinearAlgebra.inv(op::sZCY) = sZCY(op.q1, op.q2) +LinearAlgebra.inv(op::sZCZ) = sZCZ(op.q1, op.q2) +LinearAlgebra.inv(op::sXCX) = sXCX(op.q1, op.q2) +LinearAlgebra.inv(op::sXCY) = sXCY(op.q1, op.q2) +LinearAlgebra.inv(op::sXCZ) = sXCZ(op.q1, op.q2) +LinearAlgebra.inv(op::sYCX) = sYCX(op.q1, op.q2) +LinearAlgebra.inv(op::sYCY) = sYCY(op.q1, op.q2) +LinearAlgebra.inv(op::sYCZ) = sYCZ(op.q1, op.q2) +LinearAlgebra.inv(op::sZCrY) = sInvZCrY(op.q1, op.q2) +LinearAlgebra.inv(op::sInvZCrY) = sZCrY(op.q1, op.q2) +LinearAlgebra.inv(op::sSWAPCX) = sInvSWAPCX(op.q1, op.q2) +LinearAlgebra.inv(op::sInvSWAPCX) = sSWAPCX(op.q1, op.q2) +LinearAlgebra.inv(op::sCZSWAP) = sCZSWAP(op.q1, op.q2) +LinearAlgebra.inv(op::sCXSWAP) = sSWAPCX(op.q1, op.q2) +LinearAlgebra.inv(op::sISWAP) = sInvISWAP(op.q1, op.q2) +LinearAlgebra.inv(op::sInvISWAP) = sISWAP(op.q1, op.q2) +LinearAlgebra.inv(op::sSQRTZZ) = sInvSQRTZZ(op.q1, op.q2) +LinearAlgebra.inv(op::sInvSQRTZZ) = sSQRTZZ(op.q1, op.q2) ############################## # Functions that perform direct application of common operators without needing an operator instance diff --git a/test/test_symcliff.jl b/test/test_symcliff.jl index 1d9f2dbe1..40ed505ac 100644 --- a/test/test_symcliff.jl +++ b/test/test_symcliff.jl @@ -99,6 +99,19 @@ @test CliffordOperator(inv(sXCZ(n₁, n₂)), n₁) == inv(CliffordOperator(sCNOT(n₂, n₁), n₁)) @test CliffordOperator(inv(sZCrY(n₁, n₂)), n₁) == inv(CliffordOperator(sZCrY(n₁, n₂), n₁)) @test CliffordOperator(inv(sInvZCrY(n₁, n₂)), n₁) == inv(CliffordOperator(sInvZCrY(n₁, n₂), n₁)) + @test CliffordOperator(inv(sCXSWAP(n₁, n₂)), n₁) == inv(CliffordOperator(sInvSWAPCX(n₁, n₂), n₁)) end end + + @testset "Consistency check with STIM conventions" begin + # see https://github.com/quantumlib/Stim/blob/main/doc/gates.md + @test CliffordOperator(sSWAPCX) == C"IX XX ZZ ZI" + @test CliffordOperator(sInvSWAPCX) == C"XX XI IZ ZZ" + @test CliffordOperator(sCZSWAP) == C"ZX XZ IZ ZI" + @test CliffordOperator(sCXSWAP) == C"XX XI IZ ZZ" + @test CliffordOperator(sISWAP) == C"ZY YZ IZ ZI" + @test CliffordOperator(sInvISWAP) == C"-ZY -YZ IZ ZI" + @test CliffordOperator(sSQRTZZ) == C"YZ ZY ZI IZ" + @test CliffordOperator(sInvSQRTZZ) == C"-YZ -ZY ZI IZ" + end end From f3eb7cd7fd352fcc6f4994b7e42b7feca8112b7b Mon Sep 17 00:00:00 2001 From: IsaacP1234 <111547953+IsaacP1234@users.noreply.github.com> Date: Thu, 24 Oct 2024 22:46:50 -0400 Subject: [PATCH 49/66] Additional group theory functions for error correction (#351) --------- Co-authored-by: Kenneth Goodenough Co-authored-by: Stefan Krastanov Co-authored-by: Stefan Krastanov --- CHANGELOG.md | 6 + docs/src/references.bib | 25 +++ src/QuantumClifford.jl | 1 + src/grouptableaux.jl | 296 ++++++++++++++++++++++++++++++++++-- src/mul_leftright.jl | 6 + src/project_trace_reset.jl | 1 + test/test_group_tableaux.jl | 77 +++++++--- 7 files changed, 380 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06856e122..2fc286752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ ## v0.9.13 - dev +- New error correction group theory tools: + - `canonicalize_noncomm` function to find a generating set with minimal anticommutivity + - `SubsystemCodeTableau` data structure to represent the output of `canonicalize_noncomm` + - `commutify` function to find a commutative version of a non-commutative set of Paulis with minimal changes + - `matroid_parent` to, for set of Paulis that doesn't represent a state, find a version + that does. - Implementing additional named two-qubit gates: `sSWAPCX, sInvSWAPCX, sCZSWAP, sCXSWAP, sISWAP, sInvISWAP, sSQRTZZ, sInvSQRTZZ` diff --git a/docs/src/references.bib b/docs/src/references.bib index 29500a034..281ace9c1 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -403,6 +403,31 @@ @inproceedings{brown2013short doi = {10.1109/ISIT.2013.6620245} } +@article{RevModPhys.87.307, + title = {Quantum error correction for quantum memories}, + author = {Terhal, Barbara M.}, + journal = {Rev. Mod. Phys.}, + volume = {87}, + issue = {2}, + pages = {307--346}, + numpages = {40}, + year = {2015}, + month = {Apr}, + publisher = {American Physical Society}, + doi = {10.1103/RevModPhys.87.307}, + url = {https://link.aps.org/doi/10.1103/RevModPhys.87.307} +} + +@misc{goodenough2024bipartiteentanglementnoisystabilizer, + title={Bipartite entanglement of noisy stabilizer states through the lens of stabilizer codes}, + author={Kenneth Goodenough and Aqil Sajjad and Eneet Kaur and Saikat Guha and Don Towsley}, + year={2024}, + eprint={2406.02427}, + archivePrefix={arXiv}, + primaryClass={quant-ph}, + url={https://arxiv.org/abs/2406.02427}, +} + @article{panteleev2021degenerate, title = {Degenerate {{Quantum LDPC Codes With Good Finite Length Performance}}}, author = {Panteleev, Pavel and Kalachev, Gleb}, diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 6837483cb..9d892c958 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -78,6 +78,7 @@ export graphstate, graphstate!, graph_gatesequence, graph_gate, # Group theory tools groupify, minimal_generating_set, pauligroup, normalizer, centralizer, contractor, delete_columns, + canonicalize_noncomm, commutify, matroid_parent, SubsystemCodeTableau, # Clipped Gauge canonicalize_clip!, bigram, entanglement_entropy, # mctrajectories diff --git a/src/grouptableaux.jl b/src/grouptableaux.jl index 208f1ddfb..6f76802cf 100644 --- a/src/grouptableaux.jl +++ b/src/grouptableaux.jl @@ -1,3 +1,110 @@ +""" +A tableau representation of the non-commutative canonical form of a set of Paulis, +which is used in [`commutify`](@ref). + +They are organized in the same form as [`MixedDestabilizer`](@ref) +with a stabilizer, destabilizer, logical X, and logical Z components. +""" +mutable struct SubsystemCodeTableau{T <: Tableau} <: AbstractStabilizer + tab::T + index::Int + r::Int + m::Int + k::Int +end + +function SubsystemCodeTableau(t::Tableau) + index = 1 + for i in range(1, stop=length(t), step=2) + if i + 1 > length(t) + break + end + if comm(t[i], t[i+1]) == 0x01 + index = i+2 # index to split t into non-commuting pairs and commuting operators + end + end + s = Stabilizer(t[index:length(t)]) + ind = 1 + if length(s)>nqubits(s)#if stabilizer is overdetermined, Destabilizer constructor throws error + m = length(s) + tab = zero(Tableau, length(t)+length(s), nqubits(t)) + for i in s + tab[ind] = zero(PauliOperator, nqubits(s)) + ind+=1 + end + else + d = Destabilizer(s) + m = length(d) + tab = zero(Tableau, length(t)+length(d), nqubits(t)) + for p in destabilizerview(d) + tab[ind] = p + ind+=1 + end + end + for i in range(1, stop=index-1, step=2) + tab[ind] = t[i] + ind+=1 + end + for p in s + tab[ind] = p + ind+=1 + end + for i in range(2, stop=index, step=2) + tab[ind] = t[i] + ind+=1 + end + return SubsystemCodeTableau(tab, index, length(s), m, Int((index-1)/2)) +end + + +Base.copy(t::SubsystemCodeTableau) = SubsystemCodeTableau(copy(t.tab), t.index, t.r, t.m, t.k) + +function Base.show(io::IO, t::SubsystemCodeTableau) + r = t.r+t.m+2*t.k + q = nqubits(t) + if get(io, :compact, false) | haskey(io, :typeinfo) + print(io, "MixedDestablizer $r×$q") + elseif get(io, :limit, false) + h,w = displaysize(io) + println(io, "𝒟ℯ𝓈𝓉𝒶𝒷" * "━"^max(min(w-9,size(t.tab,2)-4),0)) + _show(io, destabilizerview(t).tab, w, h÷4) + if r != q + println(io) + println(io, "𝒳" * "━"^max(min(w-5,size(t.tab,2)),0)) + _show(io, logicalxview(t).tab, w, h÷4) + end + println(io) + println(io, "𝒮𝓉𝒶𝒷" * "━"^max(min(w-7,size(t.tab,2)-2),0)) + _show(io, stabilizerview(t).tab, w, h÷4) + if r != q + println(io) + println(io, "𝒵" * "━"^max(min(w-5,size(t.tab,2)),0)) + _show(io, logicalzview(t).tab, w, h÷4) + end + else + println(io, "𝒟ℯ𝓈𝓉𝒶𝒷" * "━"^max(size(t.tab,2)-4,0)) + _show(io, destabilizerview(t).tab, missing, missing) + if r != q + println(io) + println(io, "𝒳ₗ" * "━"^max(size(t.tab,2),0)) + _show(io, logicalxview(t).tab, missing, missing) + end + println(io) + println(io, "𝒮𝓉𝒶𝒷" * "━"^max(size(t.tab,2)-2,0)) + _show(io, stabilizerview(t).tab, missing, missing) + if r != q + println(io) + println(io, "𝒵ₗ" * "━"^max(size(t.tab,2))) + _show(io, logicalzview(t).tab, missing, missing) + end + end +end + +@inline stabilizerview(s::SubsystemCodeTableau) = Stabilizer(@view tab(s)[s.m+s.k+1:s.m+s.k+s.r]) +@inline destabilizerview(s::SubsystemCodeTableau) = Stabilizer(@view tab(s)[1:s.r]) +@inline logicalxview(s::SubsystemCodeTableau) = Stabilizer(tab(s)[s.m+1:s.m+s.k]) +@inline logicalzview(s::SubsystemCodeTableau) = Stabilizer(tab(s)[length(s.tab)-s.k+1:length(s.tab)]) + """ Return the full stabilizer group represented by the input generating set (a [`Stabilizer`](@ref)). @@ -11,16 +118,20 @@ julia> groupify(S"XZ ZX") + YY ``` """ -function groupify(s::Stabilizer) +function groupify(s::Stabilizer; phases=false) # Create a `Tableau` of 2ⁿ n-qubit identity Pauli operators(where n is the size of # `Stabilizer` s), then multiply each one by a different subset of the elements in s to # create all 2ⁿ unique elements in the group generated by s, then return the `Tableau`. - n = length(s)::Int - group = zero(Tableau, 2^n, nqubits(s)) + if phases == true + throw(ArgumentError("in groupify phases=true functionality not yet implemented")) + end + gen_set = minimal_generating_set(s) + n = length(gen_set)::Int + group = zero(Tableau, 2^n, nqubits(gen_set)) for i in 0:2^n-1 for (digit_order, j) in enumerate(digits(i, base=2, pad=n)) if j == 1 - group[i+1] *= s[digit_order] + mul_left!(group, i+1, tab(gen_set), digit_order, phases=Val(false)) end end end @@ -48,7 +159,7 @@ function minimal_generating_set(s::Stabilizer) if r == 0 gs = zero(Stabilizer, 1, nqubits(s)) if 0x02 in phases(s) - gs[1] = -1 * gs[1] + phases(gs)[1] = 0x2 end return gs else @@ -56,6 +167,153 @@ function minimal_generating_set(s::Stabilizer) end end +""" +For a not-necessarily commutative set of Paulis, return a generating set of the form +⟨A₁, A₂, ... Aₖ, Aₖ₊₁, ... Aₘ, B₁, B₂, ... Bₖ⟩ where pairs Aₖ, Bₖ anticommute and all other pairings commute. Based on [RevModPhys.87.307](@cite) + +Returns the generating set as a data structure of type [`SubsystemCodeTableau`](@ref). The +`logicalxview` function returns the ⟨A₁, A₂,... Aₖ⟩, and the `logicalzview` +function returns ⟨B₁, B₂, ... Bₖ⟩. `stabilizerview` returns ⟨Aₖ₊₁, ... Aₘ⟩ +as a Stabilizer, and `destabilizerview` returns the Destabilizer of that Stabilizer. + +Phases are zeroed-out in this canonicalization. + +```jldoctest +julia> canonicalize_noncomm(T"XX XZ XY") +𝒟ℯ𝓈𝓉𝒶𝒷 ++ Z_ +𝒳━━ ++ XX +𝒮𝓉𝒶𝒷 ++ X_ +𝒵━━ ++ XZ +``` +""" +function canonicalize_noncomm(t::Tableau) + for i in eachindex(t) phases(t)[i] = 0x00 end + loc = zero(Tableau, 2*length(t), nqubits(t)) + x_index = 0 + z_index = length(loc) + for i in eachindex(t) + for j in eachindex(t) + if comm(t[i], t[j]) == 0x01 + for k in eachindex(t) + if k !=i && k != j + if comm(t[k], t[i]) == 0x01 + mul_left!(t, k, j, phases=Val(false)) + end + if comm(t[k], t[j]) == 0x01 + mul_left!(t, k, i, phases=Val(false)) + end + end + end + if !(t[i] in loc) + x_index+=1 + loc[x_index]= t[i] + end + if !(t[j] in loc) + loc[z_index]= t[j] + z_index-=1 + end + end + end + end + ind = 2*x_index+1 #format tableau for SubsystemCodeTableau + k = x_index + m = length(t)-2*k + for i in range(k, stop =1, step=-1) + loc[i+m]= loc[i] + end + i = m+1 + j = m+k + while i < j #ensure anticommutative pairs are lined up correctly + loc[i], loc[j], = loc[j], loc[i] + i+=1 + j-=1 + end + s_index = m+k+1 + for i in eachindex(t) + if !(t[i] in loc) + loc[s_index]= t[i] + s_index+=1 + end + end + r = s_index-m-k-1 + if r <= nqubits(t) + d = destabilizerview(Destabilizer(Stabilizer(loc[m+k+1:s_index-1]))) + for i in eachindex(d) + loc[i] =d[i] + end + else for i in 1:m zero!(loc, i) end end + return SubsystemCodeTableau(loc, ind, r, m, k) +end + +""" +For a not-necessarily commutative set of Paulis S, +computed S', the [non-commutative canonical form](@ref canonicalize_noncomm) of of S. +For each pair Aₖ, Bₖ of anticommutative Paulis in S', add a qubit to each Pauli in the set: +X to Aₖ, Z to Bₖ, and I to each other operator to produce S'', a fully commutative set. Return +S'' as well as a list of the indices of the added qubits. + +The returned object is a Stabilizer that is also useful for the [`matroid_parent`](@ref) function. + +```jldoctest +julia> commutify(T"XX XZ XY")[1] ++ XXX ++ X__ ++ XZZ + +julia> commutify(T"XX XZ XY")[2] +3:3 +``` +""" +function commutify(t) + loc = canonicalize_noncomm(t) + commutative = zero(Stabilizer, 2*loc.k+loc.r, nqubits(loc)+loc.k) + puttableau!(commutative, logicalxview(loc), 0,0) + puttableau!(commutative, stabilizerview(loc), loc.k,0) + puttableau!(commutative, logicalzview(loc), loc.k+loc.r,0) + x= T"X" + z=T"Z" + for i in 0:(loc.k-1) + puttableau!(commutative, x, i, nqubits(loc)+i) + puttableau!(commutative, z, i+loc.r+loc.k, nqubits(loc)+i) + end + to_delete = nqubits(loc)+1:nqubits(loc)+loc.k + return commutative, to_delete +end + +""" +For a given set S of Paulis that does not necessarily represent a state, +return a set of Paulis S' that represents a state. +S' is a superset of [commutified](@ref commutify) S. +Additionally returns two arrays representing deletions needed to produce S. +Based on [goodenough2024bipartiteentanglementnoisystabilizer](@cite) + +By deleting the qubits in the first output array from S', taking the [`normalizer`](@ref) of S', then +deleting the qubits in the second returned array from the [`normalizer`](@ref) of S', S is reproduced. + +```jldoctest +julia> matroid_parent(T"XX")[1] ++ X_X ++ XX_ ++ ZZZ + +julia> matroid_parent(T"XX")[2] +3:3 + +julia> matroid_parent(T"XX")[3] +3:2 +``` +""" +function matroid_parent(t::Tableau) + com, d1= commutify(t) + norm = normalizer(com.tab) + state, d2 = commutify(norm) + return state, d2, d1 +end + """ Return the full Pauli group of a given length. Phases are ignored by default, but can be included by setting `phases=true`. @@ -127,15 +385,21 @@ julia> normalizer(T"X") + X ``` """ -function normalizer(t::Tableau) +function normalizer(t::Tableau; phases=false) # For each `PauliOperator` p in the with same number of qubits as the `Stabilizer` s, iterate through s and check each # operator's commutivity with p. If they all commute, add p a vector of `PauliOperators`. Return the vector # converted to `Tableau`. n = nqubits(t) - pgroup = pauligroup(n, phases=false) - ptype = typeof(t[1]) - normalizer = ptype[] - for p in pgroup + ptype =typeof(P"I") + norm = ptype[] + + p = zero(PauliOperator, n) + paulis = ((false, false), (true, false), (false, true), (true, true)) + for i in Iterators.product(Iterators.repeated(paulis, n)...) + zero!(p) + for (j, k) in enumerate(i) + p[j] = k + end commutes = true for q in t if comm(p, q) == 0x01 @@ -143,11 +407,15 @@ function normalizer(t::Tableau) end end if commutes - push!(normalizer, p) + push!(norm, copy(p)) + end + if phases + for phase in [-1, 1im, -1im] + push!(norm, phase *p) + end end end - - return Tableau(normalizer) + return Tableau(norm) end """ @@ -181,7 +449,7 @@ end """ Return the subset of Paulis in a Stabilizer that have identity operators on all qubits corresponding to -the given subset, without the entries corresponding to subset. +the given subset, without the entries corresponding to subset. Based on [goodenough2024bipartiteentanglementnoisystabilizer](@cite) ```jldoctest julia> contractor(S"_X X_", [1]) diff --git a/src/mul_leftright.jl b/src/mul_leftright.jl index 9803ed2b5..08f905664 100644 --- a/src/mul_leftright.jl +++ b/src/mul_leftright.jl @@ -157,6 +157,12 @@ end # On Tableaux ############################## +@inline function mul_left!(s::Tableau, m, t::Tableau, i; phases::Val{B}=Val(true)) where B + extra_phase = mul_left!((@view s.xzs[:,m]), (@view t.xzs[:,i]); phases=phases) + B && (s.phases[m] = (extra_phase+s.phases[m]+s.phases[i])&0x3) + s +end + @inline function mul_left!(s::Tableau, m, i; phases::Val{B}=Val(true)) where B extra_phase = mul_left!((@view s.xzs[:,m]), (@view s.xzs[:,i]); phases=phases) B && (s.phases[m] = (extra_phase+s.phases[m]+s.phases[i])&0x3) diff --git a/src/project_trace_reset.jl b/src/project_trace_reset.jl index 4b2e6e2e5..25aed09ce 100644 --- a/src/project_trace_reset.jl +++ b/src/project_trace_reset.jl @@ -885,5 +885,6 @@ julia> delete_columns(S"XYZ YZX ZXY", [1,3]) See also: [`traceout!`](@ref) """ function delete_columns(𝒮::Stabilizer, subset) + if length(𝒮) == 0 return 𝒮 end return 𝒮[:, setdiff(1:nqubits(𝒮), subset)] end diff --git a/test/test_group_tableaux.jl b/test/test_group_tableaux.jl index d208afdff..fb3240441 100644 --- a/test/test_group_tableaux.jl +++ b/test/test_group_tableaux.jl @@ -1,4 +1,4 @@ -@testitem "group theory routines" begin +@testitem "group theory tools" begin using Test using Random using QuantumClifford @@ -8,41 +8,82 @@ # Zero function(in groupify) slows down around 2^30(n=30),eventually breaks small_test_sizes = [1, 2, 3, 4, 5, 7] # Pauligroup slows around n = 8 - @testset "group_tableaux" begin + @testset "group theory tools" begin #Test groupify for n in [1, test_sizes...] s = random_stabilizer(n) s_test = copy(s) group = groupify(s) @test length(group) == 2^n - unchanged = true for stabilizer in group apply!(s, stabilizer) - if !(s == s_test) - unchanged = false - end - @test unchanged == true + @test s == s_test end end #Test minimal_generating_set - for n in [1, small_test_sizes...] + for n in [1, test_sizes...] s = random_stabilizer(n) group = groupify(s) gen_set = minimal_generating_set(Stabilizer(group)) + @test length(group) == 2^(length(gen_set)) new_group = groupify(gen_set) - canonicalize!(Stabilizer(group)) - canonicalize!(Stabilizer(new_group)) - @test group == new_group - s = zero(Stabilizer, rand(1:(2*n)), n) - for i in 1:length(s) - s[i] = random_pauli(n) + s1, _, r = canonicalize!(Stabilizer(group), ranks = true) + s2, _, r = canonicalize!(Stabilizer(new_group), ranks=true) + @test group[1:r, :] == new_group[1:r, :] + end + #Test canonicalize_noncomm + for n in [1, small_test_sizes...] + t = zero(QuantumClifford.Tableau, rand(1:(2*n)), n) + for i in eachindex(t) t[i] = random_pauli(n) end + loc = canonicalize_noncomm(t) + for i in 1:loc.k + for j in 1:loc.k + if i == j + @test comm(logicalxview(loc)[i], logicalzview(loc)[j]) == 0x01 + else @test comm(logicalxview(loc)[i], logicalzview(loc)[j]) == 0x00 end + end end - gen_set = minimal_generating_set(s) - new_group = groupify(s) - for operator in s - @test operator in new_group + for i in stabilizerview(loc) + for j in stabilizerview(loc) @test comm(i, j) == 0x00 end + for j in logicalzview(loc) @test comm(i, j) == 0x00 end + for j in logicalxview(loc) @test comm(i, j) == 0x00 end end end + #Test commutify + for n in [1, small_test_sizes...] + t = zero(QuantumClifford.Tableau, rand(1:(2*n)), n) + for i in eachindex(t) t[i] = random_pauli(n) end + c, d = commutify(t) + for i in c + for j in c + @test comm(i, j) == 0x00 + end + end + for i in d + for p in c + @test p[i] != (true, true) + end + end + loc1= delete_columns(c, d) + loc2 = canonicalize_noncomm(t).tab + for i in eachindex(delete_columns(c, d)) + end + end + #Test matroid_parent + for n in [1,2,3,4,5] + t = zero(QuantumClifford.Tableau, 2*n, n) + for i in eachindex(t) t[i] = random_pauli(n) end + e, d2, d1 = matroid_parent(t) + s = Stabilizer(groupify(e)) + for i in e for j in e @test comm(i, j)==0x00 end end + @test 2^(nqubits(s)) == length(s) #assumes commutativise works + #find original tableau from matroid_parentded state, ignoring phases + inverted = delete_columns(Stabilizer(normalizer(delete_columns(Stabilizer(e), d2).tab)), d1) + original = Stabilizer(groupify(Stabilizer(t))) + canonicalize!(inverted) + canonicalize!(original) + @test inverted[1:length(inverted)].tab.xzs == original[1:length(inverted)].tab.xzs + end #Test pauligroup for n in [1, small_test_sizes...] @test length(QuantumClifford.pauligroup(n, phases=false)) == 4^n From 47b8d4631c3f64df084c8a5c76edb37ce0eadc9b Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Wed, 30 Oct 2024 18:17:19 -0400 Subject: [PATCH 50/66] use explicit imports in ECC (#404) --- CHANGELOG.md | 7 +++---- Project.toml | 2 +- src/ecc/ECC.jl | 15 +++++++++++---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fc286752..9c5077104 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,16 +5,15 @@ # News -## v0.9.13 - dev +## v0.9.13 - 2024-10-30 -- New error correction group theory tools: +- New error-correction group theory tools: - `canonicalize_noncomm` function to find a generating set with minimal anticommutivity - `SubsystemCodeTableau` data structure to represent the output of `canonicalize_noncomm` - `commutify` function to find a commutative version of a non-commutative set of Paulis with minimal changes - `matroid_parent` to, for set of Paulis that doesn't represent a state, find a version that does. -- Implementing additional named two-qubit gates: `sSWAPCX, sInvSWAPCX, sCZSWAP, sCXSWAP, sISWAP, sInvISWAP, - sSQRTZZ, sInvSQRTZZ` +- Implementing additional named two-qubit gates: `sSWAPCX, sInvSWAPCX, sCZSWAP, sCXSWAP, sISWAP, sInvISWAP, sSQRTZZ, sInvSQRTZZ` ## v0.9.12 - 2024-10-18 diff --git a/Project.toml b/Project.toml index 4d300bc66..83e608beb 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.12" +version = "0.9.13" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index cdda7742e..ff4444694 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -1,9 +1,16 @@ module ECC -using LinearAlgebra -using LinearAlgebra: I -using QuantumClifford -using QuantumClifford: AbstractOperation, AbstractStabilizer, Stabilizer +using LinearAlgebra: LinearAlgebra, I, rank, tr +using QuantumClifford: QuantumClifford, AbstractOperation, AbstractStabilizer, + AbstractTwoQubitOperator, Stabilizer, PauliOperator, + random_brickwork_clifford_circuit, random_all_to_all_clifford_circuit, + canonicalize!, canonicalize_gott!, + logicalxview, logicalzview, stabilizerview, destabilizerview, tab, phases, + sCNOT, sSWAP, sHadamard, sPhase, sInvPhase, + sZCX, sZCY, sZCZ, sXCX, sXCY, sXCZ, sYCX, sYCY, sYCZ, sZ, sX, sY, sMRZ, sMRX, + single_x, single_y, single_z, random_pauli!, PauliError, + apply!, comm, comm!, stab_to_gf2, embed, @S_str, affectedqubits, affectedbits, + pftrajectories, pfmeasurements, mctrajectories import QuantumClifford: Stabilizer, MixedDestabilizer, nqubits using DocStringExtensions using Combinatorics: combinations From f69e49ccfb91d7fd85bda16a846c34d22c500399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quinten=20Prei=C3=9F?= <90014830+J-C-Q@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:30:30 +0100 Subject: [PATCH 51/66] Update noisycircuits_mc.md (#410) --- docs/src/noisycircuits_mc.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/noisycircuits_mc.md b/docs/src/noisycircuits_mc.md index 37e900df4..d24585573 100644 --- a/docs/src/noisycircuits_mc.md +++ b/docs/src/noisycircuits_mc.md @@ -15,7 +15,7 @@ Import with `using QuantumClifford.Experimental.NoisyCircuits`. This module enables the simulation of noisy Clifford circuits through a Monte Carlo method where the same circuit is evaluated multiple times with random errors interspersed through it as prescribed by a given error model. -Below is an example of a purification circuit. We first prepare the circuit we desire to use, including a noise model. `Quantikz.jl` was is used to visualize the circuit. +Below is an example of a purification circuit. We first prepare the circuit we desire to use, including a noise model. `Quantikz.jl` is used to visualize the circuit. ```@example 1 using QuantumClifford # hide @@ -55,8 +55,8 @@ If you want to create a custom gate type (e.g. calling it `Operation`), you need The `Symbol` is the status of the operation. Predefined statuses are kept in the `registered_statuses` list, but you can add more. Be sure to expand this list if you want the trajectory simulators using your custom statuses to output all trajectories. -There is also [`applynoise!`](@ref) which is convenient wait to create a noise model that can then be plugged into the [`NoisyGate`](@ref) struct, +There is also [`applynoise!`](@ref) which is a convenient way to create a noise model that can then be plugged into the [`NoisyGate`](@ref) struct, letting you reuse the predefined perfect gates and measurements. However, you can also just make up your own noise operator simply by implementing [`applywstatus!`](@ref) for it. -You can also consult the [list of implemented operators](@ref noisycircuits_ops). \ No newline at end of file +You can also consult the [list of implemented operators](@ref noisycircuits_ops). From abfcd3cc7c4b7207d6964e3d607b46b946755138 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 23:02:25 -0400 Subject: [PATCH 52/66] CompatHelper: bump compat for ILog2 to 2, (keep existing compat) (#380) Co-authored-by: CompatHelper Julia Co-authored-by: Stefan Krastanov --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 83e608beb..81df346bd 100644 --- a/Project.toml +++ b/Project.toml @@ -50,7 +50,7 @@ DocStringExtensions = "0.9" Graphs = "1.9" Hecke = "0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34" HostCPUFeatures = "0.1.6" -ILog2 = "0.2.3" +ILog2 = "0.2.3, 1, 2" InteractiveUtils = "1.9" LDPCDecoders = "0.3.1" LinearAlgebra = "1.9" From 4cc664eb6cea74703a402ecc79845f1fcb6078a1 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Sat, 2 Nov 2024 10:23:08 -0400 Subject: [PATCH 53/66] fix to affectedbit for symbolic measurements (#414) --- CHANGELOG.md | 4 ++++ Project.toml | 2 +- src/affectedqubits.jl | 3 +-- test/test_symmeas.jl | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 test/test_symmeas.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c5077104..e324370b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ # News +## v0.9.14 - 2024-11-02 + +- **(fix)** `affectedqubits()` on `sMX`, `sMY`, and `sMR*` + ## v0.9.13 - 2024-10-30 - New error-correction group theory tools: diff --git a/Project.toml b/Project.toml index 81df346bd..e49f22503 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.13" +version = "0.9.14" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/src/affectedqubits.jl b/src/affectedqubits.jl index da6314a65..3609710e3 100644 --- a/src/affectedqubits.jl +++ b/src/affectedqubits.jl @@ -15,6 +15,5 @@ affectedqubits(c::CliffordOperator) = 1:nqubits(c) affectedqubits(c::ClassicalXOR) = () affectedbits(o) = () -affectedbits(m::sMRZ) = (m.bit,) -affectedbits(m::sMZ) = (m.bit,) +affectedbits(m::Union{sMRZ,sMZ,sMRX,sMX,sMRY,sMY}) = m.bit==0 ? () : (m.bit,) affectedbits(c::ClassicalXOR) = (c.bits..., c.store) diff --git a/test/test_symmeas.jl b/test/test_symmeas.jl new file mode 100644 index 000000000..ebe035a95 --- /dev/null +++ b/test/test_symmeas.jl @@ -0,0 +1,35 @@ +@testitem "Symbolic Measurements" begin + using Random + using QuantumClifford + using Test + test_sizes = [10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. + + @testset "Symbolic Measurements" begin + for n in test_sizes + for i in [rand(1:n), 1, n, n÷2+1, n÷2-1, n÷2] + for T in [sMRZ,sMZ,sMRX,sMX] # TODO sMY sMRY + for _ in 1:10 + apply!(PauliFrame(n,n,n), T(i,1)) + end + end + end + for i in [rand(1:n), 1, n, n÷2+1, n÷2-1, n÷2] + for T in [sMRZ,sMZ,sMRX,sMX,sMY] # TODO sMRY + for _ in 1:10 + s = random_stabilizer(n) + apply!(Register(copy(s),1), T(i,1)) + end + end + end + end + @test_throws DimensionMismatch SingleQubitOperator(tCNOT,1) + @test_throws DimensionMismatch CliffordOperator(sHadamard(5),2) + @test_throws ArgumentError CliffordOperator(sHadamard(5),6,compact=true) + for T in [sMRZ,sMZ,sMRX,sMX,sMRY,sMY] + @test_throws ArgumentError T(-1) + @test_throws ArgumentError T(-1,0) + @test_throws ArgumentError T(-1,1) + @test T(1,0) == T(1) + end + end +end From d3f42d23f62d14b227adff705898d4a343d744b5 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Sat, 2 Nov 2024 19:35:34 +0500 Subject: [PATCH 54/66] better bit-wrangling abstraction using less boilerplate (#367) Co-authored-by: Stefan Krastanov --- ext/QuantumCliffordGPUExt/apply_noise.jl | 11 ++---- ext/QuantumCliffordGPUExt/pauli_frames.jl | 12 ++---- src/QuantumClifford.jl | 24 ++++++++++++ src/pauli_frames.jl | 46 ++++++++--------------- src/pauli_operator.jl | 14 ++++--- src/project_trace_reset.jl | 15 +++----- src/symbolic_cliffords.jl | 21 +++-------- 7 files changed, 68 insertions(+), 75 deletions(-) diff --git a/ext/QuantumCliffordGPUExt/apply_noise.jl b/ext/QuantumCliffordGPUExt/apply_noise.jl index 4aa99ed4b..201eb7c60 100644 --- a/ext/QuantumCliffordGPUExt/apply_noise.jl +++ b/ext/QuantumCliffordGPUExt/apply_noise.jl @@ -1,16 +1,11 @@ -using QuantumClifford: _div, _mod +using QuantumClifford: get_bitmask_idxs #according to https://github.com/JuliaGPU/CUDA.jl/blob/ac1bc29a118e7be56d9edb084a4dea4224c1d707/test/core/device/random.jl#L33 #CUDA.jl supports calling rand() inside kernel function applynoise!(frame::PauliFrameGPU{T},noise::UnbiasedUncorrelatedNoise,i::Int) where {T <: Unsigned} p = noise.p - lowbit = T(1) - ibig = _div(T,i-1)+1 - ismall = _mod(T,i-1) - ismallm = lowbit<<(ismall) - - stab = frame.frame - xzs = tab(stab).xzs + xzs = tab(frame.frame).xzs + lowbit, ibig, ismall, ismallm = get_bitmask_idxs(xzs,i) rows = size(stab, 1) @run_cuda applynoise_kernel(xzs, p, ibig, ismallm, rows) rows diff --git a/ext/QuantumCliffordGPUExt/pauli_frames.jl b/ext/QuantumCliffordGPUExt/pauli_frames.jl index 34a0565fd..4d6e03654 100644 --- a/ext/QuantumCliffordGPUExt/pauli_frames.jl +++ b/ext/QuantumCliffordGPUExt/pauli_frames.jl @@ -1,3 +1,5 @@ +using QuantumClifford: get_bitmask_idxs + ############################## # sMZ ############################## @@ -21,10 +23,7 @@ function apply!(frame::PauliFrameGPU{T}, op::QuantumClifford.sMZ) where {T <: Un op.bit == 0 && return frame i = op.qubit xzs = frame.frame.tab.xzs - lowbit = T(1) - ibig = QuantumClifford._div(T,i-1)+1 - ismall = QuantumClifford._mod(T,i-1) - ismallm = lowbit<<(ismall) + lowbit, ibig, ismall, ismallm = get_bitmask_idxs(xzs,i) (@run_cuda apply_sMZ_kernel!(xzs, frame.measurements, op, ibig, ismallm, length(frame)) length(frame)) return frame end @@ -55,10 +54,7 @@ end function apply!(frame::PauliFrameGPU{T}, op::QuantumClifford.sMRZ) where {T <: Unsigned} # TODO sMRX, sMRY i = op.qubit xzs = frame.frame.tab.xzs - lowbit = T(1) - ibig = QuantumClifford._div(T,i-1)+1 - ismall = QuantumClifford._mod(T,i-1) - ismallm = lowbit<<(ismall) + lowbit, ibig, ismall, ismallm = get_bitmask_idxs(xzs,i) (@run_cuda apply_sMRZ_kernel!(xzs, frame.measurements, op, ibig, ismallm, length(frame)) length(frame)) return frame end diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 9d892c958..6b4945b6b 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -912,6 +912,30 @@ function unsafe_bitfindnext_(chunks::AbstractVector{T}, start::Int) where T<:Uns return nothing end +""" +$(TYPEDSIGNATURES) + +Computes bitmask indices for an unsigned integer at index `i` +within the binary structure of a `Tableau` or `PauliOperator`. + +For `Tableau`, the method operates on the `.xzs` field, while +for `PauliOperator`, it uses the `.xz` field. It calculates +the following values based on the index `i`: + +- `lowbit`, the lowest bit. +- `ibig`, the index of the word containing the bit. +- `ismall`, the position of the bit within the word. +- `ismallm`, a bitmask isolating the specified bit. +""" +@inline function get_bitmask_idxs(xzs::AbstractArray{<:Unsigned}, i::Int) + T = eltype(xzs) + lowbit = T(1) + ibig = _div(T, i-1) + 1 + ismall = _mod(T, i-1) + ismallm = lowbit << ismall + return lowbit, ibig, ismall, ismallm +end + """Permute the qubits (i.e., columns) of the tableau in place.""" function Base.permute!(s::Tableau, perm::AbstractVector) for r in 1:size(s,1) diff --git a/src/pauli_frames.jl b/src/pauli_frames.jl index 45e9763c8..af84bd63b 100644 --- a/src/pauli_frames.jl +++ b/src/pauli_frames.jl @@ -82,12 +82,8 @@ end function apply!(frame::PauliFrame, op::sMZ) # TODO sMY, and faster sMX op.bit == 0 && return frame i = op.qubit - xzs = frame.frame.tab.xzs - T = eltype(xzs) - lowbit = T(1) - ibig = _div(T,i-1)+1 - ismall = _mod(T,i-1) - ismallm = lowbit<<(ismall) + xzs = tab(frame.frame).xzs + lowbit, ibig, ismall, ismallm = get_bitmask_idxs(xzs,i) @inbounds @simd for f in eachindex(frame) should_flip = !iszero(xzs[ibig,f] & ismallm) @@ -99,12 +95,8 @@ end function apply!(frame::PauliFrame, op::sMRZ) # TODO sMRY, and faster sMRX i = op.qubit - xzs = frame.frame.tab.xzs - T = eltype(xzs) - lowbit = T(1) - ibig = _div(T,i-1)+1 - ismall = _mod(T,i-1) - ismallm = lowbit<<(ismall) + xzs = tab(frame.frame).xzs + lowbit, ibig, ismall, ismallm = get_bitmask_idxs(xzs,i) if op.bit != 0 @inbounds @simd for f in eachindex(frame) @@ -122,45 +114,39 @@ end function applynoise!(frame::PauliFrame,noise::UnbiasedUncorrelatedNoise,i::Int) p = noise.p - T = eltype(frame.frame.tab.xzs) + xzs = tab(frame.frame).xzs - lowbit = T(1) - ibig = _div(T,i-1)+1 - ismall = _mod(T,i-1) - ismallm = lowbit<<(ismall) + lowbit, ibig, ismall, ismallm = get_bitmask_idxs(xzs,i) p = p/3 @inbounds @simd for f in eachindex(frame) r = rand() if r < p # X error - frame.frame.tab.xzs[ibig,f] ⊻= ismallm + xzs[ibig,f] ⊻= ismallm elseif r < 2p # Z error - frame.frame.tab.xzs[end÷2+ibig,f] ⊻= ismallm + xzs[end÷2+ibig,f] ⊻= ismallm elseif r < 3p # Y error - frame.frame.tab.xzs[ibig,f] ⊻= ismallm - frame.frame.tab.xzs[end÷2+ibig,f] ⊻= ismallm + xzs[ibig,f] ⊻= ismallm + xzs[end÷2+ibig,f] ⊻= ismallm end end return frame end function applynoise!(frame::PauliFrame,noise::PauliNoise,i::Int) - T = eltype(frame.frame.tab.xzs) + xzs = tab(frame.frame).xzs - lowbit = T(1) - ibig = _div(T,i-1)+1 - ismall = _mod(T,i-1) - ismallm = lowbit<<(ismall) + lowbit, ibig, ismall, ismallm = get_bitmask_idxs(xzs,i) @inbounds @simd for f in eachindex(frame) r = rand() if r < noise.px # X error - frame.frame.tab.xzs[ibig,f] ⊻= ismallm + xzs[ibig,f] ⊻= ismallm elseif r < noise.px+noise.pz # Z error - frame.frame.tab.xzs[end÷2+ibig,f] ⊻= ismallm + xzs[end÷2+ibig,f] ⊻= ismallm elseif r < noise.px+noise.pz+noise.py # Y error - frame.frame.tab.xzs[ibig,f] ⊻= ismallm - frame.frame.tab.xzs[end÷2+ibig,f] ⊻= ismallm + xzs[ibig,f] ⊻= ismallm + xzs[end÷2+ibig,f] ⊻= ismallm end end return frame diff --git a/src/pauli_operator.jl b/src/pauli_operator.jl index a843ef10b..2246d4d5f 100644 --- a/src/pauli_operator.jl +++ b/src/pauli_operator.jl @@ -87,19 +87,23 @@ macro P_str(a) quote _P_str($a) end end -Base.getindex(p::PauliOperator{Tₚ,Tᵥ}, i::Int) where {Tₚ, Tᵥₑ<:Unsigned, Tᵥ<:AbstractVector{Tᵥₑ}} = ((p.xz[_div(Tᵥₑ, i-1)+1] & Tᵥₑ(0x1)<<_mod(Tᵥₑ,i-1))!=0x0)::Bool, ((p.xz[end÷2+_div(Tᵥₑ,i-1)+1] & Tᵥₑ(0x1)<<_mod(Tᵥₑ,i-1))!=0x0)::Bool +function Base.getindex(p::PauliOperator{Tₚ,Tᵥ}, i::Int) where {Tₚ, Tᵥₑ<:Unsigned, Tᵥ<:AbstractVector{Tᵥₑ}} + _, ibig, _, ismallm = get_bitmask_idxs(p.xz,i) + ((p.xz[ibig] & ismallm) != 0x0)::Bool, ((p.xz[end÷2+ibig] & ismallm) != 0x0)::Bool +end Base.getindex(p::PauliOperator{Tₚ,Tᵥ}, r) where {Tₚ, Tᵥₑ<:Unsigned, Tᵥ<:AbstractVector{Tᵥₑ}} = PauliOperator(p.phase[], xbit(p)[r], zbit(p)[r]) function Base.setindex!(p::PauliOperator{Tₚ,Tᵥ}, (x,z)::Tuple{Bool,Bool}, i) where {Tₚ, Tᵥₑ, Tᵥ<:AbstractVector{Tᵥₑ}} + _, ibig, _, ismallm = get_bitmask_idxs(p.xz,i) if x - p.xz[_div(Tᵥₑ,i-1)+1] |= Tᵥₑ(0x1)<<_mod(Tᵥₑ,i-1) + p.xz[ibig] |= ismallm else - p.xz[_div(Tᵥₑ,i-1)+1] &= ~(Tᵥₑ(0x1)<<_mod(Tᵥₑ,i-1)) + p.xz[ibig] &= ~(ismallm) end if z - p.xz[end÷2+_div(Tᵥₑ,i-1)+1] |= Tᵥₑ(0x1)<<_mod(Tᵥₑ,i-1) + p.xz[end÷2+ibig] |= ismallm else - p.xz[end÷2+_div(Tᵥₑ,i-1)+1] &= ~(Tᵥₑ(0x1)<<_mod(Tᵥₑ,i-1)) + p.xz[end÷2+ibig] &= ~(ismallm) end p end diff --git a/src/project_trace_reset.jl b/src/project_trace_reset.jl index 25aed09ce..4f7fa6211 100644 --- a/src/project_trace_reset.jl +++ b/src/project_trace_reset.jl @@ -42,17 +42,15 @@ function _generate!(pauli::PauliOperator{Tₚ,Tᵥ}, stabilizer::Stabilizer{Tabl xzs = tab(stabilizer).xzs xs = @view xzs[1:end÷2,:] zs = @view xzs[end÷2+1:end,:] - lowbit = Tₘₑ(0x1) zerobit = Tₘₑ(0x0) px,pz = xview(pauli), zview(pauli) used_indices = Int[] used = 0 # remove Xs while (i=unsafe_bitfindnext_(px,1); i !== nothing) # TODO awkward notation due to https://github.com/JuliaLang/julia/issues/45499 - jbig = _div(Tₘₑ,i-1)+1 - jsmall = lowbit<<_mod(Tₘₑ,i-1) - candidate = findfirst(e->e&jsmall!=zerobit, # TODO some form of reinterpret might be faster than equality check - xs[jbig,used+1:end]) + _, ibig, _, ismallm = get_bitmask_idxs(xzs,i) + candidate = findfirst(e->e&ismallm!=zerobit, # TODO some form of reinterpret might be faster than equality check + xs[ibig,used+1:end]) if isnothing(candidate) return nothing else @@ -63,10 +61,9 @@ function _generate!(pauli::PauliOperator{Tₚ,Tᵥ}, stabilizer::Stabilizer{Tabl end # remove Zs while (i=unsafe_bitfindnext_(pz,1); i !== nothing) # TODO awkward notation due to https://github.com/JuliaLang/julia/issues/45499 - jbig = _div(Tₘₑ,i-1)+1 - jsmall = lowbit<<_mod(Tₘₑ,i-1) - candidate = findfirst(e->e&jsmall!=zerobit, # TODO some form of reinterpret might be faster than equality check - zs[jbig,used+1:end]) + _, ibig, _, ismallm = get_bitmask_idxs(xzs,i) + candidate = findfirst(e->e&ismallm!=zerobit, # TODO some form of reinterpret might be faster than equality check + zs[ibig,used+1:end]) if isnothing(candidate) return nothing else diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index 98f0e5d61..03f5f2e69 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -415,12 +415,9 @@ LinearAlgebra.inv(op::sInvSQRTZZ) = sSQRTZZ(op.q1, op.q2) """Apply a Pauli Z to the `i`-th qubit of state `s`. You should use `apply!(stab,sZ(i))` instead of this.""" function apply_single_z!(stab::AbstractStabilizer, i) s = tab(stab) - Tₘₑ = eltype(s.xzs) - bigi = _div(Tₘₑ,i-1)+1 - smalli = _mod(Tₘₑ,i-1) - mask = Tₘₑ(0x1)< Date: Sat, 2 Nov 2024 20:10:02 +0500 Subject: [PATCH 55/66] implement sSQRTXX, sInvSQRTXX, sSQRTYY, sInvSQRTYY (#368) --- src/QuantumClifford.jl | 2 +- src/symbolic_cliffords.jl | 10 ++++++++++ test/test_symcliff.jl | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 6b4945b6b..36901ed23 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -52,7 +52,7 @@ export sCNOT, sCPHASE, sSWAP, sXCX, sXCY, sXCZ, sYCX, sYCY, sYCZ, sZCX, sZCY, sZCZ, sZCrY, sInvZCrY, sSWAPCX, sInvSWAPCX, sCZSWAP, sCXSWAP, sISWAP, sInvISWAP, - sSQRTZZ, sInvSQRTZZ, + sSQRTZZ, sInvSQRTZZ, sSQRTXX, sInvSQRTXX, sSQRTYY, sInvSQRTYY, # Misc Ops SparseGate, sMX, sMY, sMZ, PauliMeasurement, Reset, sMRX, sMRY, sMRZ, diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index 03f5f2e69..fcff4cb38 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -339,6 +339,12 @@ end @qubitop2 SQRTZZ (x1 , x1⊻x2⊻z1 , x2 , x1⊻z2⊻x2 , ~iszero((x1 & z1 & ~x2) | (~x1 & x2 & z2))) @qubitop2 InvSQRTZZ (x1 , x1⊻x2⊻z1 , x2 , x1⊻z2⊻x2 , ~iszero((x1 &~z1 & ~x2) | (~x1 & x2 &~z2))) +@qubitop2 SQRTXX (z1⊻z2⊻x1, z1 , z1⊻x2⊻z2, z2 , ~iszero((~x1 & z1 &~z2) | (~z1 &~x2 & z2))) +@qubitop2 InvSQRTXX (z1⊻z2⊻x1, z1 , z1⊻x2⊻z2, z2 , ~iszero(( x1 & z1 &~z2) | (~z1 & x2 & z2))) + +@qubitop2 SQRTYY (z1⊻x2⊻z2, x1⊻z2⊻x2, x1⊻z1⊻z2, x1⊻x2⊻z1, ~iszero((~x1 &~z1 & x2 &~z2) | ( x1 &~z1 &~x2 &~z2) | ( x1 &~z1 & x2 & z2) | ( x1 & z1 & x2 &~z2))) +@qubitop2 InvSQRTYY (z1⊻x2⊻z2, x1⊻z2⊻x2, x1⊻z1⊻z2, x1⊻x2⊻z1, ~iszero(( x1 & z1 &~x2 & z2) | (~x1 & z1 & x2 & z2) | (~x1 & z1 &~x2 &~z2) | (~x1 &~z1 &~x2 & z2))) + #= To get the boolean formulas for the phase, it is easiest to first write down the truth table for the phase: for i in 0:15 @@ -405,6 +411,10 @@ LinearAlgebra.inv(op::sISWAP) = sInvISWAP(op.q1, op.q2) LinearAlgebra.inv(op::sInvISWAP) = sISWAP(op.q1, op.q2) LinearAlgebra.inv(op::sSQRTZZ) = sInvSQRTZZ(op.q1, op.q2) LinearAlgebra.inv(op::sInvSQRTZZ) = sSQRTZZ(op.q1, op.q2) +LinearAlgebra.inv(op::sSQRTXX) = sInvSQRTXX(op.q1, op.q2) +LinearAlgebra.inv(op::sInvSQRTXX) = sSQRTXX(op.q1, op.q2) +LinearAlgebra.inv(op::sSQRTYY) = sInvSQRTYY(op.q1, op.q2) +LinearAlgebra.inv(op::sInvSQRTYY) = sSQRTYY(op.q1, op.q2) ############################## # Functions that perform direct application of common operators without needing an operator instance diff --git a/test/test_symcliff.jl b/test/test_symcliff.jl index 40ed505ac..6bbad80d4 100644 --- a/test/test_symcliff.jl +++ b/test/test_symcliff.jl @@ -76,6 +76,7 @@ @test CliffordOperator(inv(random_op), i) == inv(CliffordOperator(random_op, i)) @test CliffordOperator(inv(SingleQubitOperator(random_op)), i) == inv(CliffordOperator(random_op, i)) end + end @testset "Consistency checks with Stim" begin # see https://github.com/quantumlib/Stim/blob/main/doc/gates.md @@ -113,5 +114,9 @@ @test CliffordOperator(sInvISWAP) == C"-ZY -YZ IZ ZI" @test CliffordOperator(sSQRTZZ) == C"YZ ZY ZI IZ" @test CliffordOperator(sInvSQRTZZ) == C"-YZ -ZY ZI IZ" + @test CliffordOperator(sSQRTXX) == C"XI IX -YX -XY" + @test CliffordOperator(sInvSQRTXX) == C"XI IX YX XY" + @test CliffordOperator(sSQRTYY) == C"-ZY -YZ XY YX" + @test CliffordOperator(sInvSQRTYY) == C"ZY YZ -XY -YX" end end From 56acb6ebf9b3aa75d79a8ddc0d03cd5bb83aecb7 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Sun, 3 Nov 2024 05:42:36 +0500 Subject: [PATCH 56/66] fix #191: too restrictive typeassert for MixedDestabilizer (#366) Co-authored-by: Stefan Krastanov --- src/QuantumClifford.jl | 4 ++-- test/test_stabs.jl | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 36901ed23..6b90af7a8 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -571,8 +571,8 @@ function MixedDestabilizer(stab::Stabilizer{T}; undoperm=true, reportperm=false) t[n+r+s+1:end] = sZ # The other logical set in the tableau end if undoperm - t = t[:,invperm(permx[permz])]::T - return MixedDestabilizer(t, r+s)::MixedDestabilizer{T} + t = t[:,invperm(permx[permz])] + return MixedDestabilizer(t, r+s) end if reportperm return (MixedDestabilizer(t, r+s)::MixedDestabilizer{T}, r, permx, permz) diff --git a/test/test_stabs.jl b/test/test_stabs.jl index b479934c2..d6b9041de 100644 --- a/test/test_stabs.jl +++ b/test/test_stabs.jl @@ -1,4 +1,5 @@ @testitem "Stabilizers" begin + using QuantumClifford using QuantumClifford: stab_looks_good, destab_looks_good, mixed_stab_looks_good, mixed_destab_looks_good test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off-by-one errors in the bit encoding. @testset "Pure and Mixed state initialization" begin @@ -107,4 +108,17 @@ @test hcat(copy(tab(s1)), copy(tab(s2))) == T"-YZZY XXYX" @test hcat(copy(tab(s1)), copy(tab(s2)), copy(tab(s1)), copy(tab(s2))) == T"YZZYYZZY XXYXXXYX" end + + @testset "MixedDestabilizer over subarrays (#191)" begin + # Case 1: QuantumClifford.Tableau{Vector{UInt8}, Matrix{UInt64}} + n = 6 + stab = random_stabilizer(n) + regular_arr = MixedDestabilizer(stab; undoperm=true) + @test isa(regular_arr, MixedDestabilizer) + # Case 2: Tableau{SubArray{...}, SubArray{...}, Tuple{Base.Slice{...}}} + stab = random_stabilizer(n) + substab = @view stab[3:n] + md_via_subarr = MixedDestabilizer(substab; undoperm=true) + @test isa(md_via_subarr, MixedDestabilizer) + end end From 406e5eaf23f3c461906b7e58f72230072571568d Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Sun, 3 Nov 2024 10:27:20 -0500 Subject: [PATCH 57/66] update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e324370b0..7bf3a570d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,11 @@ # News -## v0.9.14 - 2024-11-02 +## v0.9.14 - 2024-11-03 - **(fix)** `affectedqubits()` on `sMX`, `sMY`, and `sMR*` +- **(fix)** restrictive type-assert in `MixedDestabilizer` failing on views of tableaux +- Implementing additional named two-qubit gates: `sSQRTXX, sInvSQRTXX, sSQRTYY, sInvSQRTYY` ## v0.9.13 - 2024-10-30 From 553133622ebe66c3b35675ada9a739027e3f9367 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Tue, 5 Nov 2024 09:05:05 +0500 Subject: [PATCH 58/66] add `check_repr_commutation_relation` to test the CSS orthogonality condition for 2BGA's Group Algebra with a General Group `G` (#403) Co-authored-by: Stefan Krastanov --- .../QuantumCliffordHeckeExt.jl | 5 +++-- ext/QuantumCliffordHeckeExt/util.jl | 15 +++++++++++++++ src/ecc/codes/util.jl | 3 +++ test/test_ecc_base.jl | 2 ++ 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 ext/QuantumCliffordHeckeExt/util.jl diff --git a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl index 29e9de8ce..b8508ff66 100644 --- a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl +++ b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl @@ -5,14 +5,15 @@ using DocStringExtensions import QuantumClifford, LinearAlgebra import Hecke: Group, GroupElem, AdditiveGroup, AdditiveGroupElem, GroupAlgebra, GroupAlgebraElem, FqFieldElem, representation_matrix, dim, base_ring, - multiplication_table, coefficients, abelian_group, group_algebra + multiplication_table, coefficients, abelian_group, group_algebra, rand import Nemo import Nemo: characteristic, matrix_repr, GF, ZZ, lift import QuantumClifford.ECC: AbstractECC, CSS, ClassicalCode, hgp, code_k, code_n, code_s, iscss, parity_checks, parity_checks_x, parity_checks_z, parity_checks_xz, - two_block_group_algebra_codes, generalized_bicycle_codes, bicycle_codes + two_block_group_algebra_codes, generalized_bicycle_codes, bicycle_codes, check_repr_commutation_relation +include("util.jl") include("types.jl") include("lifted.jl") include("lifted_product.jl") diff --git a/ext/QuantumCliffordHeckeExt/util.jl b/ext/QuantumCliffordHeckeExt/util.jl new file mode 100644 index 000000000..d8fc65e85 --- /dev/null +++ b/ext/QuantumCliffordHeckeExt/util.jl @@ -0,0 +1,15 @@ +""" +Checks the commutation relation between the left and right representation matrices +for two randomly-sampled elements `a` and `b` in the group algebra `ℱ[G]` with a general group `G`. +It verifies the commutation relation that states, `L(a)·R(b) = R(b)·L(a)`. This +property shows that matrices from the left and right representation sets commute +with each other, which is an important property related to the CSS orthogonality +condition. +""" +function check_repr_commutation_relation(GA::GroupAlgebra) + a, b = rand(GA), rand(GA) + # Check commutation relation: L(a)R(b) = R(b)L(a) + L_a = representation_matrix(a) + R_b = representation_matrix(b, :right) + return L_a * R_b == R_b * L_a +end diff --git a/src/ecc/codes/util.jl b/src/ecc/codes/util.jl index 1063785f9..b96a75120 100644 --- a/src/ecc/codes/util.jl +++ b/src/ecc/codes/util.jl @@ -6,3 +6,6 @@ function hgp(h₁,h₂) hz = hcat(kron(LinearAlgebra.I(n₁), h₂), kron(h₁', LinearAlgebra.I(r₂))) hx, hz end + +"""Implemented in a package extension with Hecke.""" +function check_repr_commutation_relation end diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index 9f4a5ec9e..26b00c8f3 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -1,6 +1,7 @@ using Test using QuantumClifford using QuantumClifford.ECC +using QuantumClifford.ECC: check_repr_commutation_relation using InteractiveUtils import Nemo: GF @@ -46,6 +47,7 @@ other_lifted_product_codes = [] # [[882, 24, d≤24]] code from (B1) in Appendix B of [panteleev2021degenerate](@cite) l = 63 GA = group_algebra(GF(2), abelian_group(l)) +@test check_repr_commutation_relation(GA) # TODO use this check more pervasively throughout the test suite A = zeros(GA, 7, 7) x = gens(GA)[] A[LinearAlgebra.diagind(A)] .= x^27 From 40924b804c128b3aa2186cd647b7e3252bbd0180 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Tue, 5 Nov 2024 09:07:30 +0500 Subject: [PATCH 59/66] =?UTF-8?q?add=20tests=20and=20examples=20of=20=20C?= =?UTF-8?q?=E2=82=98=20=C3=97=20C=E2=82=82=202BGA=20codes=20(#392)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stefan Krastanov --- docs/src/references.bib | 11 ++ ext/QuantumCliffordHeckeExt/lifted_product.jl | 28 ++++- test/test_ecc_2bga.jl | 119 ++++++++++++++++++ 3 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 test/test_ecc_2bga.jl diff --git a/docs/src/references.bib b/docs/src/references.bib index 281ace9c1..d82fc7440 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -512,3 +512,14 @@ @article{anderson2014fault year={2014}, publisher={APS} } + +@article{lin2024quantum, + title={Quantum two-block group algebra codes}, + author={Lin, Hsiang-Ku and Pryadko, Leonid P}, + journal={Physical Review A}, + volume={109}, + number={2}, + pages={022407}, + year={2024}, + publisher={APS} +} diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index ef09eddbb..21cd96481 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -153,12 +153,32 @@ code_s(c::LPCode) = size(c.repr(zero(c.GA)), 1) * (size(c.A, 1) * size(c.B, 1) + Two-block group algebra (2GBA) codes, which are a special case of lifted product codes from two group algebra elements `a` and `b`, used as `1x1` base matrices. +Here is an example of a [[56, 28, 2]] 2BGA code from Table 2 of [lin2024quantum](@cite) +with direct product of `C₄ x C₂`. + +```jldoctest +julia> import Hecke: group_algebra, GF, abelian_group, gens; + +julia> GA = group_algebra(GF(2), abelian_group([14,2])); + +julia> x = gens(GA)[1]; + +julia> s = gens(GA)[2]; + +julia> A = 1 + x^7 + +julia> B = 1 + x^7 + s + x^8 + s*x^7 + x + +julia> c = two_block_group_algebra_codes(A,B); + +julia> code_n(c), code_k(c) +(56, 28) +``` + See also: [`LPCode`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref) -""" # TODO doctest example +""" function two_block_group_algebra_codes(a::GroupAlgebraElem, b::GroupAlgebraElem) - A = reshape([a], (1, 1)) - B = reshape([b], (1, 1)) - LPCode(A, B) + LPCode([a;;], [b;;]) end """ diff --git a/test/test_ecc_2bga.jl b/test/test_ecc_2bga.jl new file mode 100644 index 000000000..e284abfab --- /dev/null +++ b/test/test_ecc_2bga.jl @@ -0,0 +1,119 @@ +@testitem "ECC 2BGA" begin + using Hecke + using Hecke: group_algebra, GF, abelian_group, gens + using QuantumClifford.ECC: LPCode, code_k, code_n + + @testset "Reproduce Table 2 lin2024quantum" begin # TODO these tests should probably just use the `two_block_group_algebra_codes` function as that would make them much shorter and simpler + # codes taken from Table 2 of [lin2024quantum](@cite) + + # m = 4 + GA = group_algebra(GF(2), abelian_group([4,2])) + x = gens(GA)[1] + s = gens(GA)[2] + A = [1 + x;;] + B = [1 + x + s + x^2 + s*x + s*x^3;;] + c = LPCode(A,B) + # [[16, 2, 4]] 2BGA code + @test code_n(c) == 16 && code_k(c) == 2 + A = [1 + x;;] + B = [1 + x + s + x^2 + s*x + x^3;;] + c = LPCode(A,B) + # [[16, 4, 4]] 2BGA code + @test code_n(c) == 16 && code_k(c) == 4 + A = [1 + s;;] + B = [1 + x + s + x^2 + s*x + x^2;;] + c = LPCode(A,B) + # [[16, 8, 2]] 2BGA code + @test code_n(c) == 16 && code_k(c) == 8 + + # m = 6 + GA = group_algebra(GF(2), abelian_group([6,2])) + x = gens(GA)[1] + s = gens(GA)[2] + A = [1 + x;;] + B = [1 + x^3 + s + x^4 + x^2 + s*x;;] + c = LPCode(A,B) + # [[24, 4, 5]] 2BGA code + @test code_n(c) == 24 && code_k(c) == 4 + A = [1 + x^3;;] + B = [1 + x^3 + s + x^4 + s*x^3 + x;;] + c = LPCode(A,B) + # [[24, 12, 2]] 2BGA code + @test code_n(c) == 24 && code_k(c) == 12 + + # m = 8 + GA = group_algebra(GF(2), abelian_group([8,2])) + x = gens(GA)[1] + s = gens(GA)[2] + A = [1 + x^6;;] + B = [1 + s*x^7 + s*x^4 + x^6 + s*x^5 + s*x^2;;] + c = LPCode(A,B) + # [[32, 8, 4]] 2BGA code + @test code_n(c) == 32 && code_k(c) == 8 + A = [1 + s*x^4;;] + B = [1 + s*x^7 + s*x^4 + x^6 + x^3 + s*x^2;;] + c = LPCode(A,B) + # [[32, 16, 2]] 2BGA code + @test code_n(c) == 32 && code_k(c) == 16 + + # m = 10 + GA = group_algebra(GF(2), abelian_group([10,2])) + x = gens(GA)[1] + s = gens(GA)[2] + A = [1 + x;;] + B = [1 + x^5 + x^6 + s*x^6 + x^7 + s*x^3;;] + c = LPCode(A,B) + # [[40, 4, 8]] 2BGA code + @test code_n(c) == 40 && code_k(c) == 4 + A = [1 + x^6;;] + B = [1 + x^5 + s + x^6 + x + s*x^2;;] + c = LPCode(A,B) + # [[40, 8, 5]] 2BGA code + @test code_n(c) == 40 && code_k(c) == 8 + A = [1 + x^5;;] + B = [1 + x^5 + s + x^6 + s*x^5 + x;;] + c = LPCode(A,B) + # [[40, 20, 2]] 2BGA code + @test code_n(c) == 40 && code_k(c) == 20 + + # m = 12 + GA = group_algebra(GF(2), abelian_group([12,2])) + x = gens(GA)[1] + s = gens(GA)[2] + A = [1 + s*x^10;;] + B = [1 + x^3 + s*x^6 + x^4 + x^7 + x^8;;] + c = LPCode(A,B) + # [[48, 8, 6]] 2BGA code + @test code_n(c) == 48 && code_k(c) == 8 + A = [1 + x^3;;] + B = [1 + x^3 + s*x^6 + x^4 + s*x^9 + x^7;;] + c = LPCode(A,B) + # [[48, 12, 4]] 2BGA code + @test code_n(c) == 48 && code_k(c) == 12 + A = [1 + x^4;;] + B = [1 + x^3 + s*x^6 + x^4 + x^7 + s*x^10;;] + c = LPCode(A,B) + # [[48, 16, 3]] 2BGA code + @test code_n(c) == 48 && code_k(c) == 16 + A = [1 + s*x^6;;] + B = [1 + x^3 + s*x^6 + x^4 + s*x^9 + s*x^10;;] + c = LPCode(A,B) + # [[48, 24, 2]] 2BGA code + @test code_n(c) == 48 && code_k(c) == 24 + + # m = 14 + GA = group_algebra(GF(2), abelian_group([14,2])) + x = gens(GA)[1] + s = gens(GA)[2] + A = [1 + x^8;;] + B = [1 + x^7 + s + x^8 + x^9 + s*x^4;;] + c = LPCode(A,B) + # [[56, 8, 7]] 2BGA code + @test code_n(c) == 56 && code_k(c) == 8 + A = [1 + x^7;;] + B = [1 + x^7 + s + x^8 + s*x^7 + x;;] + c = LPCode(A,B) + # [[56, 28, 2]] 2BGA code + @test code_n(c) == 56 && code_k(c) == 28 + end +end From 5f2ef93856973da27e96f8f406916a87a7ab6f17 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Tue, 5 Nov 2024 17:50:48 +0500 Subject: [PATCH 60/66] Implementing Bivariate Bicycle Codes using 2BGA as the parent via Hecke's Group Algebra (#399) Co-authored-by: Stefan Krastanov Co-authored-by: Stefan Krastanov --- docs/src/references.bib | 11 ++ docs/src/references.md | 2 + ext/QuantumCliffordHeckeExt/lifted_product.jl | 39 +++- test/test_ecc_base.jl | 29 ++- test/test_ecc_bivaraite_bicycle_as_twobga.jl | 173 ++++++++++++++++++ 5 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 test/test_ecc_bivaraite_bicycle_as_twobga.jl diff --git a/docs/src/references.bib b/docs/src/references.bib index d82fc7440..635ca65ee 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -523,3 +523,14 @@ @article{lin2024quantum year={2024}, publisher={APS} } + +@article{bravyi2024high, + title={High-threshold and low-overhead fault-tolerant quantum memory}, + author={Bravyi, Sergey and Cross, Andrew W and Gambetta, Jay M and Maslov, Dmitri and Rall, Patrick and Yoder, Theodore J}, + journal={Nature}, + volume={627}, + number={8005}, + pages={778--782}, + year={2024}, + publisher={Nature Publishing Group UK London} +} diff --git a/docs/src/references.md b/docs/src/references.md index 35e944a21..049f222b5 100644 --- a/docs/src/references.md +++ b/docs/src/references.md @@ -40,6 +40,8 @@ For quantum code construction routines: - [steane1999quantum](@cite) - [campbell2012magic](@cite) - [anderson2014fault](@cite) +- [lin2024quantum](@cite) +- [bravyi2024high](@cite) For classical code construction routines: - [muller1954application](@cite) diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index 21cd96481..f67bf9d39 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -150,14 +150,18 @@ code_n(c::LPCode) = size(c.repr(zero(c.GA)), 2) * (size(c.A, 2) * size(c.B, 1) + code_s(c::LPCode) = size(c.repr(zero(c.GA)), 1) * (size(c.A, 1) * size(c.B, 1) + size(c.A, 2) * size(c.B, 2)) """ -Two-block group algebra (2GBA) codes, which are a special case of lifted product codes +Two-block group algebra (2BGA) codes, which are a special case of lifted product codes from two group algebra elements `a` and `b`, used as `1x1` base matrices. +## Examples of 2BGA code subfamilies + +### `C₄ x C₂` + Here is an example of a [[56, 28, 2]] 2BGA code from Table 2 of [lin2024quantum](@cite) with direct product of `C₄ x C₂`. ```jldoctest -julia> import Hecke: group_algebra, GF, abelian_group, gens; +julia> import Hecke: group_algebra, GF, abelian_group, gens julia> GA = group_algebra(GF(2), abelian_group([14,2])); @@ -175,7 +179,36 @@ julia> code_n(c), code_k(c) (56, 28) ``` -See also: [`LPCode`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref) +### Bivariate Bicycle codes + +Bivariate Bicycle codes are a class of Abelian 2BGA codes formed by the direct product +of two cyclic groups `ℤₗ × ℤₘ`. The parameters `l` and `m` represent the orders of the +first and second cyclic groups, respectively. + +The ECC Zoo has an [entry for this family](https://errorcorrectionzoo.org/c/qcga). + +A [[756, 16, ≤ 34]] code from Table 3 of [bravyi2024high](@cite): + +```jldoctest +julia> import Hecke: group_algebra, GF, abelian_group, gens + +julia> l=21; m=18; + +julia> GA = group_algebra(GF(2), abelian_group([l, m])); + +julia> x, y = gens(GA); + +julia> A = x^3 + y^10 + y^17; + +julia> B = y^5 + x^3 + x^19; + +julia> c = two_block_group_algebra_codes(A,B); + +julia> code_n(c), code_k(c) +(756, 16) +``` + +See also: [`LPCode`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref). """ function two_block_group_algebra_codes(a::GroupAlgebraElem, b::GroupAlgebraElem) LPCode([a;;], [b;;]) diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index 26b00c8f3..ca423aa25 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -58,6 +58,33 @@ A[LinearAlgebra.diagind(A, 5)] .= GA(1) B = reshape([1 + x + x^6], (1, 1)) push!(other_lifted_product_codes, LPCode(A, B)) +# Bivariate Bicycle codes +# A [[72, 12, 6]] code from Table 3 of [bravyi2024high](@cite). +l=6; m=6 +GA = group_algebra(GF(2), abelian_group([l, m])) +x, y = gens(GA) +A = x^3 + y + y^2 +B = y^3 + x + x^2 +bb1 = two_block_group_algebra_codes(A,B) + +# A [[90, 8, 10]] code from Table 3 of [bravyi2024high](@cite). +l=15; m=3 +GA = group_algebra(GF(2), abelian_group([l, m])) +x, y = gens(GA) +A = x^9 + y + y^2 +B = 1 + x^2 + x^7 +bb2 = two_block_group_algebra_codes(A,B) + +# A [[360, 12, ≤ 24]] code from Table 3 of [bravyi2024high](@cite). +l=30; m=6 +GA = group_algebra(GF(2), abelian_group([l, m])) +x, y = gens(GA) +A = x^9 + y + y^2 +B = y^3 + x^25 + x^26 +bb3 = two_block_group_algebra_codes(A,B) + +test_bb_codes = [bb1, bb2, bb3] + const code_instance_args = Dict( :Toric => [(3,3), (4,4), (3,6), (4,3), (5,5)], :Surface => [(3,3), (4,4), (3,6), (4,3), (5,5)], @@ -65,7 +92,7 @@ const code_instance_args = Dict( :CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4, 4)]), :Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2, 2), Shor9())], :CircuitCode => random_circuit_code_args, - :LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, other_lifted_product_codes)), + :LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, test_bb_codes, other_lifted_product_codes)), :QuantumReedMuller => [3, 4, 5] ) diff --git a/test/test_ecc_bivaraite_bicycle_as_twobga.jl b/test/test_ecc_bivaraite_bicycle_as_twobga.jl new file mode 100644 index 000000000..ffeac975a --- /dev/null +++ b/test/test_ecc_bivaraite_bicycle_as_twobga.jl @@ -0,0 +1,173 @@ +@testitem "ECC Bivaraite Bicycle as 2BGA" begin + using Hecke + using Hecke: group_algebra, GF, abelian_group, gens, one + using QuantumClifford.ECC: two_block_group_algebra_codes, code_k, code_n + + @testset "Reproduce Table 3 bravyi2024high" begin + # [[72, 12, 6]] + l=6; m=6 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^3 + y + y^2 + B = y^3 + x + x^2 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 72 && code_k(c) == 12 + + # [[90, 8, 10]] + l=15; m=3 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^9 + y + y^2 + B = 1 + x^2 + x^7 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 90 && code_k(c) == 8 + + # [[108, 8, 10]] + l=9; m=6 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^3 + y + y^2 + B = y^3 + x + x^2 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 108 && code_k(c) == 8 + + # [[144, 12, 12]] + l=12; m=6 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^3 + y + y^2 + B = y^3 + x + x^2 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 144 && code_k(c) == 12 + + # [[288, 12, 12]] + l=12; m=12 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^3 + y^2 + y^7 + B = y^3 + x + x^2 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 288 && code_k(c) == 12 + + # [[360, 12, ≤ 24]] + l=30; m=6 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^9 + y + y^2 + B = y^3 + x^25 + x^26 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 360 && code_k(c) == 12 + + # [[756, 16, ≤ 34]] + l=21; m=18 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^3 + y^10 + y^17 + B = y^5 + x^3 + x^19 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 756 && code_k(c) == 16 + end + + @testset "Reproduce Table 1 berthusen2024toward" begin + # [[72, 8, 6]] + l=12; m=3 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^9 + y + y^2 + B = 1 + x + x^11 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 72 && code_k(c) == 8 + + # [[90, 8, 6]] + l=9; m=5 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^8 + y^4 + y + B = y^5 + x^8 + x^7 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 90 && code_k(c) == 8 + + # [[120, 8, 8]] + l=12; m=5 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^10 + y^4 + y + B = 1 + x + x^2 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 120 && code_k(c) == 8 + + # [[150, 8, 8]] + l=15; m=5 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^5 + y^2 + y^3 + B = y^2 + x^7 + x^6 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 150 && code_k(c) == 8 + + # [[196, 12, 8]] + l=14; m=7 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^6 + y^5 + y^6 + B = 1 + x^4 + x^13 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 196 && code_k(c) == 12 + end + + @testset "Reproduce Table 1 wang2024coprime" begin + # [[54, 8, 6]] + l=3; m=9 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = 1 + y^2 + y^4 + B = y^3 + x + x^2 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 54 && code_k(c) == 8 + + # [[98, 6, 12]] + l=7; m=7 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^3 + y^5 + y^6 + B = y^2 + x^3 + x^5 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 98 && code_k(c) == 6 + + # [[126, 8, 10]] + l=3; m=21 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = 1 + y^2 + y^10 + B = y^3 + x + x^2 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 126 && code_k(c) == 8 + + # [[150, 16, 8]] + l=5; m=15 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = 1 + y^6 + y^8 + B = y^5 + x + x^4 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 150 && code_k(c) == 16 + + # [[162, 8, 14]] + l=3; m=27 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = 1 + y^10 + y^14 + B = y^12 + x + x^2 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 162 && code_k(c) == 8 + + # [[180, 8, 16]] + l=6; m=15 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + A = x^3 + y + y^2 + B = y^6 + x^4 + x^5 + c = two_block_group_algebra_codes(A,B) + @test code_n(c) == 180 && code_k(c) == 8 + end +end From 138e0088231dbbb72b1a58b6a0eee9af41a616bf Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Tue, 5 Nov 2024 17:56:46 +0500 Subject: [PATCH 61/66] Multivariate Bicycle code via Hecke's Group Algebra (#381) Co-authored-by: Stefan Krastanov --- docs/src/references.bib | 10 ++ docs/src/references.md | 1 + ext/QuantumCliffordHeckeExt/lifted_product.jl | 23 +++ test/test_ecc_base.jl | 41 ++++- test/test_ecc_multivariate_bicycle.jl | 154 ++++++++++++++++++ 5 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 test/test_ecc_multivariate_bicycle.jl diff --git a/docs/src/references.bib b/docs/src/references.bib index 635ca65ee..465df5584 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -524,6 +524,16 @@ @article{lin2024quantum publisher={APS} } +@misc{voss2024multivariatebicyclecodes, + title={Multivariate Bicycle Codes}, + author={Lukas Voss and Sim Jian Xian and Tobias Haug and Kishor Bharti}, + year={2024}, + eprint={2406.19151}, + archivePrefix={arXiv}, + primaryClass={quant-ph}, + url={https://arxiv.org/abs/2406.19151}, +} + @article{bravyi2024high, title={High-threshold and low-overhead fault-tolerant quantum memory}, author={Bravyi, Sergey and Cross, Andrew W and Gambetta, Jay M and Maslov, Dmitri and Rall, Patrick and Yoder, Theodore J}, diff --git a/docs/src/references.md b/docs/src/references.md index 049f222b5..3f2742653 100644 --- a/docs/src/references.md +++ b/docs/src/references.md @@ -40,6 +40,7 @@ For quantum code construction routines: - [steane1999quantum](@cite) - [campbell2012magic](@cite) - [anderson2014fault](@cite) +- [voss2024multivariatebicyclecodes](@cite) - [lin2024quantum](@cite) - [bravyi2024high](@cite) diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index f67bf9d39..0c8916f60 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -207,6 +207,29 @@ julia> c = two_block_group_algebra_codes(A,B); julia> code_n(c), code_k(c) (756, 16) ``` +### Multivariate Bicycle code + +The group algebra of the qubit multivariate bicycle (MB) code with r variables is `𝔽₂[𝐺ᵣ]`, +where `𝐺ᵣ = ℤ/l₁ × ℤ/l₂ × ... × ℤ/lᵣ`. + +A [[48, 4, 6]] Weight-6 TB-QLDPC code from Appendix A Table 2 of [voss2024multivariatebicyclecodes](@cite). + +```jldoctest +julia> import Hecke: group_algebra, GF, abelian_group, gens; # hide + +julia> l=4; m=6; + +julia> z = x*y; + +julia> A = x^3 + y^5; + +julia> B = x + z^5 + y^5 + y^2; + +julia> c = two_block_group_algebra_codes(A, B); + +julia> code_n(c), code_k(c) +(48, 4) +``` See also: [`LPCode`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref). """ diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index ca423aa25..f52040b93 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -58,6 +58,45 @@ A[LinearAlgebra.diagind(A, 5)] .= GA(1) B = reshape([1 + x + x^6], (1, 1)) push!(other_lifted_product_codes, LPCode(A, B)) +# Multivariate Bicycle codes taken from Table 1 of [voss2024multivariatebicyclecodes](@cite) +# Weight-4 [[144, 2, 12]] MBB code +l=8; m=9 +GA = group_algebra(GF(2), abelian_group([l, m])) +x, y = gens(GA) +z = x*y +A = x^3 + y^7 +B = x + y^5 +weight4mbb = two_block_group_algebra_codes(A, B) + +# Weight-5 [96, 4, 8]] MBB code +l=8; m=6 +GA = group_algebra(GF(2), abelian_group([l, m])) +x, y = gens(GA) +z = x*y +A = x^6 + x^3 +B = z^5 + x^5 + y +weight5mbb = two_block_group_algebra_codes(A, B) + +# Weight-6 [[48, 4, 6]] MBB code +l=4; m=6 +GA = group_algebra(GF(2), abelian_group([l, m])) +x, y = gens(GA) +z = x*y +A = x^3 + y^5 +B = x + z^5 + y^5 + y^2 +weight6mbb = two_block_group_algebra_codes(A, B) + +# Weight-7 [[30, 4, 5]] MBB code +l=5; m=3 +GA = group_algebra(GF(2), abelian_group([l, m])); +x, y = gens(GA) +z = x*y +A = x^4 + x^2 +B = x + x^2 + y + z^2 + z^3 +weight7mbb = two_block_group_algebra_codes(A, B) + +test_mbb_codes = [weight4mbb, weight5mbb, weight6mbb, weight7mbb] + # Bivariate Bicycle codes # A [[72, 12, 6]] code from Table 3 of [bravyi2024high](@cite). l=6; m=6 @@ -92,7 +131,7 @@ const code_instance_args = Dict( :CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4, 4)]), :Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2, 2), Shor9())], :CircuitCode => random_circuit_code_args, - :LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, test_bb_codes, other_lifted_product_codes)), + :LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, test_bb_codes, test_mbb_codes, other_lifted_product_codes)), :QuantumReedMuller => [3, 4, 5] ) diff --git a/test/test_ecc_multivariate_bicycle.jl b/test/test_ecc_multivariate_bicycle.jl new file mode 100644 index 000000000..e004579f4 --- /dev/null +++ b/test/test_ecc_multivariate_bicycle.jl @@ -0,0 +1,154 @@ +@testitem "ECC Multivaraite Bicycle" begin + using Hecke + using Hecke: group_algebra, GF, abelian_group, gens + using QuantumClifford.ECC: two_block_group_algebra_codes, code_k, code_n + + # Multivariate Bicycle codes taken from Table 1 of [voss2024multivariatebicyclecodes](@cite) + @testset "Weight-4 QLDPC Codes" begin + # [[112, 8, 5]] + l=7; m=8 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = z^2 + z^6 + B = x + x^6 + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 112 && code_k(c) == 8 + + # [[64, 2, 8]] + l=8; m=4 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = x + x^2 + B = x^3 + y + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 64 && code_k(c) == 2 + + # [[72, 2, 8]] + l=4; m=9 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = x + y^2 + B = x^2 + y^2 + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 72 && code_k(c) == 2 + + # [[96, 2, 8]] + l=6; m=8 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = x^5 + y^6 + B = z + z^4 + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 96 && code_k(c) == 2 + + # [[112, 2, 10]] + l=7; m=8 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = z^6 + x^5 + B = z^2 + y^5 + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 112 && code_k(c) == 2 + + # [[144, 2, 12]] + l=8; m=9 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = x^3 + y^7 + B = x + y^5 + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 144 && code_k(c) == 2 + end + + @testset "Weight-5 QLDPC Codes" begin + # [[30, 4, 5]] + l=3; m=5 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = x + z^4 + B = x + y^2 + z^2 + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 30 && code_k(c) == 4 + + # [[72, 4, 8]] + l=4; m=9 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = x + y^3 + B = x^2 + y + y^2 + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 72 && code_k(c) == 4 + + # [96, 4, 8]] + l=8; m=6 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = x^6 + x^3 + B = z^5 + x^5 + y + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 96 && code_k(c) == 4 + end + + @testset "Weight-6 QLDPC codes" begin + # [[30, 6, 4]] + l=5; m=3 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = x^4 + z^3 + B = x^4 + x + z^4 + y + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 30 && code_k(c) == 6 + + # [[48, 6, 6]] + l=4; m=6 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = x^2 + y^4 + B = x^3 + z^3 + y^2 + y + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 48 && code_k(c) == 6 + + # [[40, 4, 6]] + l=4; m=5 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = x^2 + y + B = y^4 + y^2 + x^3 + x + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 40 && code_k(c) == 4 + + # [[48, 4, 6]] + l=4; m=6 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = x^3 + y^5 + B = x + z^5 + y^5 + y^2 + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 48 && code_k(c) == 4 + end + + @testset "Weight-7 QLDPC codes" begin + # [[30, 4, 5]] + l=5; m=3 + GA = group_algebra(GF(2), abelian_group([l, m])) + x, y = gens(GA) + z = x*y + A = x^4 + x^2 + B = x + x^2 + y + z^2 + z^3 + c = two_block_group_algebra_codes(A, B) + @test code_n(c) == 30 && code_k(c) == 4 + end +end From be34f1637d262216d81a53357181b68169cd581d Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Tue, 5 Nov 2024 18:13:04 +0500 Subject: [PATCH 62/66] Coprime Bivariate Bicycle code via Hecke's Group Algebra (#378) Co-authored-by: Stefan Krastanov --- docs/src/references.bib | 7 +++ docs/src/references.md | 1 + ext/QuantumCliffordHeckeExt/lifted_product.jl | 27 +++++++++ test/test_ecc_base.jl | 21 ++++++- test/test_ecc_coprime_bivaraite_bicycle.jl | 59 +++++++++++++++++++ 5 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 test/test_ecc_coprime_bivaraite_bicycle.jl diff --git a/docs/src/references.bib b/docs/src/references.bib index 465df5584..83be080da 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -524,6 +524,13 @@ @article{lin2024quantum publisher={APS} } +@article{wang2024coprime, + title={Coprime Bivariate Bicycle Codes and their Properties}, + author={Wang, Ming and Mueller, Frank}, + journal={arXiv preprint arXiv:2408.10001}, + year={2024} +} + @misc{voss2024multivariatebicyclecodes, title={Multivariate Bicycle Codes}, author={Lukas Voss and Sim Jian Xian and Tobias Haug and Kishor Bharti}, diff --git a/docs/src/references.md b/docs/src/references.md index 3f2742653..16f2195be 100644 --- a/docs/src/references.md +++ b/docs/src/references.md @@ -40,6 +40,7 @@ For quantum code construction routines: - [steane1999quantum](@cite) - [campbell2012magic](@cite) - [anderson2014fault](@cite) +- [wang2024coprime](@cite) - [voss2024multivariatebicyclecodes](@cite) - [lin2024quantum](@cite) - [bravyi2024high](@cite) diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index 0c8916f60..e5a9b2878 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -207,6 +207,7 @@ julia> c = two_block_group_algebra_codes(A,B); julia> code_n(c), code_k(c) (756, 16) ``` + ### Multivariate Bicycle code The group algebra of the qubit multivariate bicycle (MB) code with r variables is `𝔽₂[𝐺ᵣ]`, @@ -231,6 +232,32 @@ julia> code_n(c), code_k(c) (48, 4) ``` +### Coprime Bivariate Bicycle code + +The coprime bivariate bicycle (BB) codes are defined by two polynomials `𝑎(𝑥,𝑦)` and `𝑏(𝑥,𝑦)`, +where `𝑙` and `𝑚` are coprime, and can be expressed as univariate polynomials `𝑎(𝜋)` and `𝑏(𝜋)`, +with generator `𝜋 = 𝑥𝑦`. They can be viewed as a special case of Lifted Product construction +based on abelian group `ℤₗ x ℤₘ` where `ℤⱼ` cyclic group of order `j`. + +[108, 12, 6]] coprime-bivariate bicycle (BB) code from Table 2 of [wang2024coprime](@cite). + +```jldoctest +julia> import Hecke: group_algebra, GF, abelian_group, gens; + +julia> l=2; m=27; + +julia> GA = group_algebra(GF(2), abelian_group([l*m])); + +julia> 𝜋 = gens(GA)[1]; + +julia> A = 𝜋^2 + 𝜋^5 + 𝜋^44; + +julia> B = 𝜋^8 + 𝜋^14 + 𝜋^47; + + +(108, 12) +``` + See also: [`LPCode`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref). """ function two_block_group_algebra_codes(a::GroupAlgebraElem, b::GroupAlgebraElem) diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index f52040b93..ab247da71 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -58,6 +58,25 @@ A[LinearAlgebra.diagind(A, 5)] .= GA(1) B = reshape([1 + x + x^6], (1, 1)) push!(other_lifted_product_codes, LPCode(A, B)) +# coprime Bivariate Bicycle codes from Table 2 of [wang2024coprime](@cite) +# [[108,12,6]] +l=2; m=27 +GA = group_algebra(GF(2), abelian_group([l*m])) +𝜋 = gens(GA)[1] +A = 𝜋^2 + 𝜋^5 + 𝜋^44 +B = 𝜋^8 + 𝜋^14 + 𝜋^47 +coprimeBB1 = two_block_group_algebra_codes(A, B) + +# [[126,12,10]] +l=7; m=9 +GA = group_algebra(GF(2), abelian_group([l*m])) +𝜋 = gens(GA)[1] +A = 1 + 𝜋 + 𝜋^58 +B = 𝜋^3 + 𝜋^16 + 𝜋^44 +coprimeBB2 = two_block_group_algebra_codes(A, B) + +test_coprimeBB_codes = [coprimeBB1, coprimeBB2] + # Multivariate Bicycle codes taken from Table 1 of [voss2024multivariatebicyclecodes](@cite) # Weight-4 [[144, 2, 12]] MBB code l=8; m=9 @@ -131,7 +150,7 @@ const code_instance_args = Dict( :CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4, 4)]), :Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2, 2), Shor9())], :CircuitCode => random_circuit_code_args, - :LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, test_bb_codes, test_mbb_codes, other_lifted_product_codes)), + :LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, test_bb_codes, test_mbb_codes, test_coprimeBB_codes, other_lifted_product_codes)), :QuantumReedMuller => [3, 4, 5] ) diff --git a/test/test_ecc_coprime_bivaraite_bicycle.jl b/test/test_ecc_coprime_bivaraite_bicycle.jl new file mode 100644 index 000000000..6e82637ad --- /dev/null +++ b/test/test_ecc_coprime_bivaraite_bicycle.jl @@ -0,0 +1,59 @@ +@testitem "ECC coprime Bivaraite Bicycle" begin + using Nemo + using Nemo: gcd + using Hecke + using Hecke: group_algebra, GF, abelian_group, gens + using QuantumClifford.ECC: two_block_group_algebra_codes, code_k, code_n + + @testset "Reproduce Table 2 wang2024coprime" begin + # [[30,4,6]] + l=3; m=5; + GA = group_algebra(GF(2), abelian_group([l*m])) + 𝜋 = gens(GA)[1] + A = 1 + 𝜋 + 𝜋^2 + B = 𝜋 + 𝜋^3 + 𝜋^8 + c = two_block_group_algebra_codes(A, B) + @test gcd([l,m]) == 1 + @test code_n(c) == 30 && code_k(c) == 4 + + # [[42,6,6]] + l=3; m=7; + GA = group_algebra(GF(2), abelian_group([l*m])) + 𝜋 = gens(GA)[1] + A = 1 + 𝜋^2 + 𝜋^3 + B = 𝜋 + 𝜋^3 + 𝜋^11 + c = two_block_group_algebra_codes(A, B) + @test gcd([l,m]) == 1 + @test code_n(c) == 42 && code_k(c) == 6 + + # [[70,6,8]] + l=5; m=7; + GA = group_algebra(GF(2), abelian_group([l*m])) + 𝜋 = gens(GA)[1] + A = 1 + 𝜋 + 𝜋^5; + B = 1 + 𝜋 + 𝜋^12; + c = two_block_group_algebra_codes(A, B) + @test gcd([l,m]) == 1 + @test code_n(c) == 70 && code_k(c) == 6 + + # [[108,12,6]] + l=2; m=27; + GA = group_algebra(GF(2), abelian_group([l*m])) + 𝜋 = gens(GA)[1] + A = 𝜋^2 + 𝜋^5 + 𝜋^44 + B = 𝜋^8 + 𝜋^14 + 𝜋^47 + c = two_block_group_algebra_codes(A, B) + @test gcd([l,m]) == 1 + @test code_n(c) == 108 && code_k(c) == 12 + + # [[126,12,10]] + l=7; m=9 + GA = group_algebra(GF(2), abelian_group([l*m])) + 𝜋 = gens(GA)[1] + A = 1 + 𝜋 + 𝜋^58 + B = 𝜋^3 + 𝜋^16 + 𝜋^44 + c = two_block_group_algebra_codes(A, B) + @test gcd([l,m]) == 1 + @test code_n(c) == 126 && code_k(c) == 12 + end +end From c3aa0558681b0972ebba2f3f32f6358ff52bb00e Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Tue, 5 Nov 2024 18:14:15 +0500 Subject: [PATCH 63/66] examples of generalized bicycle codes (#389) Co-authored-by: Stefan Krastanov --- ext/QuantumCliffordHeckeExt/lifted_product.jl | 14 +++++++++++++- test/test_ecc_generalized_bicycle_codes.jl | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 test/test_ecc_generalized_bicycle_codes.jl diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index e5a9b2878..047dd2a7c 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -265,7 +265,7 @@ function two_block_group_algebra_codes(a::GroupAlgebraElem, b::GroupAlgebraElem) end """ -Generalized bicycle codes, which are a special case of 2GBA codes (and therefore of lifted product codes). +Generalized bicycle codes, which are a special case of *abelian* 2GBA codes (and therefore of lifted product codes). Here the group is chosen as the cyclic group of order `l`, and the base matrices `a` and `b` are the sum of the group algebra elements corresponding to the shifts `a_shifts` and `b_shifts`. @@ -279,6 +279,18 @@ julia> c = generalized_bicycle_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], julia> code_n(c), code_k(c) (254, 28) ``` + +An [[70, 8, 10]] *abelian* 2BGA code from Table 1 of [lin2024quantum](@cite), with cyclic group of +order `l = 35`, illustrates that *abelian* 2BGA codes can be viewed as GB codes. + +```jldoctest +julia> l = 35; + +julia> c1 = generalized_bicycle_codes([0, 15, 16, 18], [0, 1, 24, 27], l); + +julia> code_n(c1), code_k(c1) +(70, 8) +``` """ function generalized_bicycle_codes(a_shifts::Array{Int}, b_shifts::Array{Int}, l::Int) GA = group_algebra(GF(2), abelian_group(l)) diff --git a/test/test_ecc_generalized_bicycle_codes.jl b/test/test_ecc_generalized_bicycle_codes.jl new file mode 100644 index 000000000..c6e1f50bf --- /dev/null +++ b/test/test_ecc_generalized_bicycle_codes.jl @@ -0,0 +1,18 @@ +@testitem "ECC GB" begin + using Hecke + using QuantumClifford.ECC: generalized_bicycle_codes, code_k, code_n + + # codes taken from Table 1 of [lin2024quantum](@cite) + # Abelian 2BGA codes can be viewed as GB codes. + @test code_n(generalized_bicycle_codes([0 , 15, 16, 18], [0 , 1, 24, 27], 35)) == 70 + @test code_k(generalized_bicycle_codes([0 , 15, 16, 18], [0 , 1, 24, 27], 35)) == 8 + @test code_n(generalized_bicycle_codes([0 , 1, 3, 7], [0 , 1, 12, 19], 27)) == 54 + @test code_k(generalized_bicycle_codes([0 , 1, 3, 7], [0 , 1, 12, 19], 27)) == 6 + @test code_n(generalized_bicycle_codes([0 , 10, 6, 13], [0 , 25, 16, 12], 30)) == 60 + @test code_k(generalized_bicycle_codes([0 , 10, 6, 13], [0 , 25, 16, 12], 30)) == 6 + @test code_n(generalized_bicycle_codes([0 , 9, 28, 31], [0 , 1, 21, 34], 36)) == 72 + @test code_k(generalized_bicycle_codes([0 , 9, 28, 31], [0 , 1, 21, 34], 36)) == 8 + @test code_n(generalized_bicycle_codes([0 , 9, 28, 13], [0 , 1, 21, 34], 36)) == 72 + @test code_k(generalized_bicycle_codes([0 , 9, 28, 13], [0 , 1, 3, 22], 36)) == 10 + end +end From 45e9f4d64c902ba95ae5bedc1e59895f552147c4 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Tue, 5 Nov 2024 18:15:50 +0500 Subject: [PATCH 64/66] todo: simplify tests for 2BGA codes (#421) --- ext/QuantumCliffordHeckeExt/lifted_product.jl | 4 +- test/test_ecc_2bga.jl | 118 +++++++++--------- 2 files changed, 57 insertions(+), 65 deletions(-) diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index 047dd2a7c..9d5fa0d0c 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -165,9 +165,7 @@ julia> import Hecke: group_algebra, GF, abelian_group, gens julia> GA = group_algebra(GF(2), abelian_group([14,2])); -julia> x = gens(GA)[1]; - -julia> s = gens(GA)[2]; +julia> x, s = gens(GA); julia> A = 1 + x^7 diff --git a/test/test_ecc_2bga.jl b/test/test_ecc_2bga.jl index e284abfab..9b3f0a609 100644 --- a/test/test_ecc_2bga.jl +++ b/test/test_ecc_2bga.jl @@ -1,118 +1,112 @@ @testitem "ECC 2BGA" begin using Hecke using Hecke: group_algebra, GF, abelian_group, gens - using QuantumClifford.ECC: LPCode, code_k, code_n + using QuantumClifford.ECC: two_block_group_algebra_codes, code_k, code_n - @testset "Reproduce Table 2 lin2024quantum" begin # TODO these tests should probably just use the `two_block_group_algebra_codes` function as that would make them much shorter and simpler + @testset "Reproduce Table 2 lin2024quantum" begin # codes taken from Table 2 of [lin2024quantum](@cite) # m = 4 GA = group_algebra(GF(2), abelian_group([4,2])) - x = gens(GA)[1] - s = gens(GA)[2] - A = [1 + x;;] - B = [1 + x + s + x^2 + s*x + s*x^3;;] - c = LPCode(A,B) + x, s = gens(GA) + A = 1 + x + B = 1 + x + s + x^2 + s*x + s*x^3 + c = two_block_group_algebra_codes(A,B) # [[16, 2, 4]] 2BGA code @test code_n(c) == 16 && code_k(c) == 2 - A = [1 + x;;] - B = [1 + x + s + x^2 + s*x + x^3;;] - c = LPCode(A,B) + A = 1 + x + B = 1 + x + s + x^2 + s*x + x^3 + c = two_block_group_algebra_codes(A,B) # [[16, 4, 4]] 2BGA code @test code_n(c) == 16 && code_k(c) == 4 - A = [1 + s;;] - B = [1 + x + s + x^2 + s*x + x^2;;] - c = LPCode(A,B) + A = 1 + s + B = 1 + x + s + x^2 + s*x + x^2 + c = two_block_group_algebra_codes(A,B) # [[16, 8, 2]] 2BGA code @test code_n(c) == 16 && code_k(c) == 8 # m = 6 GA = group_algebra(GF(2), abelian_group([6,2])) - x = gens(GA)[1] - s = gens(GA)[2] - A = [1 + x;;] - B = [1 + x^3 + s + x^4 + x^2 + s*x;;] - c = LPCode(A,B) + x, s = gens(GA) + A = 1 + x + B = 1 + x^3 + s + x^4 + x^2 + s*x + c = two_block_group_algebra_codes(A,B) # [[24, 4, 5]] 2BGA code @test code_n(c) == 24 && code_k(c) == 4 - A = [1 + x^3;;] - B = [1 + x^3 + s + x^4 + s*x^3 + x;;] - c = LPCode(A,B) + A = 1 + x^3 + B = 1 + x^3 + s + x^4 + s*x^3 + x + c = two_block_group_algebra_codes(A,B) # [[24, 12, 2]] 2BGA code @test code_n(c) == 24 && code_k(c) == 12 # m = 8 GA = group_algebra(GF(2), abelian_group([8,2])) - x = gens(GA)[1] - s = gens(GA)[2] - A = [1 + x^6;;] - B = [1 + s*x^7 + s*x^4 + x^6 + s*x^5 + s*x^2;;] - c = LPCode(A,B) + x, s = gens(GA) + A = 1 + x^6 + B = 1 + s*x^7 + s*x^4 + x^6 + s*x^5 + s*x^2 + c = two_block_group_algebra_codes(A,B) # [[32, 8, 4]] 2BGA code @test code_n(c) == 32 && code_k(c) == 8 - A = [1 + s*x^4;;] - B = [1 + s*x^7 + s*x^4 + x^6 + x^3 + s*x^2;;] - c = LPCode(A,B) + A = 1 + s*x^4 + B = 1 + s*x^7 + s*x^4 + x^6 + x^3 + s*x^2 + c = two_block_group_algebra_codes(A,B) # [[32, 16, 2]] 2BGA code @test code_n(c) == 32 && code_k(c) == 16 # m = 10 GA = group_algebra(GF(2), abelian_group([10,2])) - x = gens(GA)[1] - s = gens(GA)[2] - A = [1 + x;;] - B = [1 + x^5 + x^6 + s*x^6 + x^7 + s*x^3;;] - c = LPCode(A,B) + x, s = gens(GA) + A = 1 + x + B = 1 + x^5 + x^6 + s*x^6 + x^7 + s*x^3 + c = two_block_group_algebra_codes(A,B) # [[40, 4, 8]] 2BGA code @test code_n(c) == 40 && code_k(c) == 4 - A = [1 + x^6;;] - B = [1 + x^5 + s + x^6 + x + s*x^2;;] - c = LPCode(A,B) + A = 1 + x^6 + B = 1 + x^5 + s + x^6 + x + s*x^2 + c = two_block_group_algebra_codes(A,B) # [[40, 8, 5]] 2BGA code @test code_n(c) == 40 && code_k(c) == 8 - A = [1 + x^5;;] - B = [1 + x^5 + s + x^6 + s*x^5 + x;;] - c = LPCode(A,B) + A = 1 + x^5 + B = 1 + x^5 + s + x^6 + s*x^5 + x + c = two_block_group_algebra_codes(A,B) # [[40, 20, 2]] 2BGA code @test code_n(c) == 40 && code_k(c) == 20 # m = 12 GA = group_algebra(GF(2), abelian_group([12,2])) - x = gens(GA)[1] - s = gens(GA)[2] - A = [1 + s*x^10;;] - B = [1 + x^3 + s*x^6 + x^4 + x^7 + x^8;;] - c = LPCode(A,B) + x, s = gens(GA) + A = 1 + s*x^10 + B = 1 + x^3 + s*x^6 + x^4 + x^7 + x^8 + c = two_block_group_algebra_codes(A,B) # [[48, 8, 6]] 2BGA code @test code_n(c) == 48 && code_k(c) == 8 - A = [1 + x^3;;] - B = [1 + x^3 + s*x^6 + x^4 + s*x^9 + x^7;;] - c = LPCode(A,B) + A = 1 + x^3 + B = 1 + x^3 + s*x^6 + x^4 + s*x^9 + x^7 + c = two_block_group_algebra_codes(A,B) # [[48, 12, 4]] 2BGA code @test code_n(c) == 48 && code_k(c) == 12 - A = [1 + x^4;;] - B = [1 + x^3 + s*x^6 + x^4 + x^7 + s*x^10;;] - c = LPCode(A,B) + A = 1 + x^4 + B = 1 + x^3 + s*x^6 + x^4 + x^7 + s*x^10 + c = two_block_group_algebra_codes(A,B) # [[48, 16, 3]] 2BGA code @test code_n(c) == 48 && code_k(c) == 16 - A = [1 + s*x^6;;] - B = [1 + x^3 + s*x^6 + x^4 + s*x^9 + s*x^10;;] - c = LPCode(A,B) + A = 1 + s*x^6 + B = 1 + x^3 + s*x^6 + x^4 + s*x^9 + s*x^10 + c = two_block_group_algebra_codes(A,B) # [[48, 24, 2]] 2BGA code @test code_n(c) == 48 && code_k(c) == 24 # m = 14 GA = group_algebra(GF(2), abelian_group([14,2])) - x = gens(GA)[1] - s = gens(GA)[2] - A = [1 + x^8;;] - B = [1 + x^7 + s + x^8 + x^9 + s*x^4;;] - c = LPCode(A,B) + x, s = gens(GA) + A = 1 + x^8 + B = 1 + x^7 + s + x^8 + x^9 + s*x^4 + c = two_block_group_algebra_codes(A,B) # [[56, 8, 7]] 2BGA code @test code_n(c) == 56 && code_k(c) == 8 - A = [1 + x^7;;] - B = [1 + x^7 + s + x^8 + s*x^7 + x;;] - c = LPCode(A,B) + A = 1 + x^7 + B = 1 + x^7 + s + x^8 + s*x^7 + x + c = two_block_group_algebra_codes(A,B) # [[56, 28, 2]] 2BGA code @test code_n(c) == 56 && code_k(c) == 28 end From ce1dc02c8255e644d3045a4809ed2b64ceb41e72 Mon Sep 17 00:00:00 2001 From: Feroz Ahmad Date: Tue, 5 Nov 2024 22:08:46 +0500 Subject: [PATCH 65/66] =?UTF-8?q?Haah's=20cubic=20code=20using=20abelian?= =?UTF-8?q?=20group=20=E2=84=A4=E2=82=83=CB=A3=C2=B3=20via=20Hecke's=20Gro?= =?UTF-8?q?up=20Algebra=20(#388)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stefan Krastanov --- docs/src/references.bib | 10 ++++++ docs/src/references.md | 1 + .../QuantumCliffordHeckeExt.jl | 3 +- ext/QuantumCliffordHeckeExt/lifted_product.jl | 32 ++++++++++++++++--- src/ecc/ECC.jl | 1 + src/ecc/codes/lifted_product.jl | 3 ++ test/test_ecc_base.jl | 6 +++- 7 files changed, 49 insertions(+), 7 deletions(-) diff --git a/docs/src/references.bib b/docs/src/references.bib index 83be080da..05276ff75 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -551,3 +551,13 @@ @article{bravyi2024high year={2024}, publisher={Nature Publishing Group UK London} } + +@article{haah2011local, + title={Local stabilizer codes in three dimensions without string logical operators}, + author={Haah, Jeongwan}, + journal={Physical Review A?Atomic, Molecular, and Optical Physics}, + volume={83}, + number={4}, + pages={042330}, + year={2011}, +} diff --git a/docs/src/references.md b/docs/src/references.md index 16f2195be..97c679136 100644 --- a/docs/src/references.md +++ b/docs/src/references.md @@ -44,6 +44,7 @@ For quantum code construction routines: - [voss2024multivariatebicyclecodes](@cite) - [lin2024quantum](@cite) - [bravyi2024high](@cite) +- [haah2011local](@cite) For classical code construction routines: - [muller1954application](@cite) diff --git a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl index b8508ff66..89d7bae0c 100644 --- a/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl +++ b/ext/QuantumCliffordHeckeExt/QuantumCliffordHeckeExt.jl @@ -11,7 +11,8 @@ import Nemo: characteristic, matrix_repr, GF, ZZ, lift import QuantumClifford.ECC: AbstractECC, CSS, ClassicalCode, hgp, code_k, code_n, code_s, iscss, parity_checks, parity_checks_x, parity_checks_z, parity_checks_xz, - two_block_group_algebra_codes, generalized_bicycle_codes, bicycle_codes, check_repr_commutation_relation + two_block_group_algebra_codes, generalized_bicycle_codes, bicycle_codes, check_repr_commutation_relation, + haah_cubic_codes include("util.jl") include("types.jl") diff --git a/ext/QuantumCliffordHeckeExt/lifted_product.jl b/ext/QuantumCliffordHeckeExt/lifted_product.jl index 9d5fa0d0c..66bd35ed6 100644 --- a/ext/QuantumCliffordHeckeExt/lifted_product.jl +++ b/ext/QuantumCliffordHeckeExt/lifted_product.jl @@ -77,7 +77,8 @@ The default representation, provided by `Hecke`, is the permutation representati We also accept a custom representation function as detailed in [`LiftedCode`](@ref). -See also: [`LiftedCode`](@ref), [`two_block_group_algebra_codes`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref). +See also: [`LiftedCode`](@ref), [`two_block_group_algebra_codes`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref), +[`haah_cubic_codes`](@ref). $TYPEDFIELDS """ @@ -251,12 +252,10 @@ julia> 𝜋 = gens(GA)[1]; julia> A = 𝜋^2 + 𝜋^5 + 𝜋^44; julia> B = 𝜋^8 + 𝜋^14 + 𝜋^47; - - (108, 12) ``` -See also: [`LPCode`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref). +See also: [`LPCode`](@ref), [`generalized_bicycle_codes`](@ref), [`bicycle_codes`](@ref), [`haah_cubic_codes`](@ref). """ function two_block_group_algebra_codes(a::GroupAlgebraElem, b::GroupAlgebraElem) LPCode([a;;], [b;;]) @@ -302,10 +301,33 @@ Bicycle codes are a special case of generalized bicycle codes, where `a` and `b` are conjugate to each other. The order of the cyclic group is `l`, and the shifts `a_shifts` and `b_shifts` are reverse to each other. -See also: [`two_block_group_algebra_codes`](@ref), [`generalized_bicycle_codes`](@ref). +See also: [`two_block_group_algebra_codes`](@ref), [`generalized_bicycle_codes`](@ref), [`haah_cubic_codes`](@ref). """ # TODO doctest example function bicycle_codes(a_shifts::Array{Int}, l::Int) GA = group_algebra(GF(2), abelian_group(l)) a = sum(GA[n÷l+1] for n in a_shifts) two_block_group_algebra_codes(a, group_algebra_conj(a)) end + +""" +Haah’s cubic codes [haah2011local](@cite) can be viewed as generalized bicycle (GB) codes +with the group `G = Cₗ × Cₗ × Cₗ`, where `l` denotes the lattice size. In particular, a GB +code with the group `G = ℤ₃ˣ³` corresponds to a cubic code. + +The ECC Zoo has an [entry for this family](https://errorcorrectionzoo.org/c/haah_cubic). + +```jldoctest +julia> c = haah_cubic_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], 6); + +julia> code_n(c), code_k(c) +(432, 8) +``` + +See also: [`bicycle_codes`](@ref), [`generalized_bicycle_codes`](@ref), [`two_block_group_algebra_codes`](@ref). +""" +function haah_cubic_codes(a_shifts::Array{Int}, b_shifts::Array{Int}, l::Int) + GA = group_algebra(GF(2), abelian_group([l,l,l])) + a = sum(GA[n%dim(GA)+1] for n in a_shifts) + b = sum(GA[n%dim(GA)+1] for n in b_shifts) + two_block_group_algebra_codes(a, b) +end diff --git a/src/ecc/ECC.jl b/src/ecc/ECC.jl index ff4444694..231816318 100644 --- a/src/ecc/ECC.jl +++ b/src/ecc/ECC.jl @@ -29,6 +29,7 @@ export parity_checks, parity_checks_x, parity_checks_z, iscss, Shor9, Steane7, Cleve8, Perfect5, Bitflip3, Toric, Gottesman, Surface, Concat, CircuitCode, QuantumReedMuller, LPCode, two_block_group_algebra_codes, generalized_bicycle_codes, bicycle_codes, + haah_cubic_codes, random_brickwork_circuit_code, random_all_to_all_circuit_code, evaluate_decoder, CommutationCheckECCSetup, NaiveSyndromeECCSetup, ShorSyndromeECCSetup, diff --git a/src/ecc/codes/lifted_product.jl b/src/ecc/codes/lifted_product.jl index 338880702..2a0c2c36f 100644 --- a/src/ecc/codes/lifted_product.jl +++ b/src/ecc/codes/lifted_product.jl @@ -17,3 +17,6 @@ function generalized_bicycle_codes end """Implemented in a package extension with Hecke.""" function bicycle_codes end + +"""Implemented in a package extension with Hecke.""" +function haah_cubic_codes end diff --git a/test/test_ecc_base.jl b/test/test_ecc_base.jl index ab247da71..7c1d12ef5 100644 --- a/test/test_ecc_base.jl +++ b/test/test_ecc_base.jl @@ -42,6 +42,10 @@ test_gb_codes = [ generalized_bicycle_codes([0, 1, 14, 16, 22], [0, 3, 13, 20, 42], 63), # (A2) [[126, 28, 8]] ] +test_hcubic_codes = [ + haah_cubic_codes([0, 15, 20, 28, 66], [0, 58, 59, 100, 121], 3) +] + other_lifted_product_codes = [] # [[882, 24, d≤24]] code from (B1) in Appendix B of [panteleev2021degenerate](@cite) @@ -150,7 +154,7 @@ const code_instance_args = Dict( :CSS => (c -> (parity_checks_x(c), parity_checks_z(c))).([Shor9(), Steane7(), Toric(4, 4)]), :Concat => [(Perfect5(), Perfect5()), (Perfect5(), Steane7()), (Steane7(), Cleve8()), (Toric(2, 2), Shor9())], :CircuitCode => random_circuit_code_args, - :LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, test_bb_codes, test_mbb_codes, test_coprimeBB_codes, other_lifted_product_codes)), + :LPCode => (c -> (c.A, c.B)).(vcat(LP04, LP118, test_gb_codes, test_bb_codes, test_mbb_codes, test_coprimeBB_codes, test_hcubic_codes, other_lifted_product_codes)), :QuantumReedMuller => [3, 4, 5] ) From 5fcf3531737f54ff8797e27d5cb25b45fb85a9a7 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Thu, 21 Nov 2024 12:59:22 -0500 Subject: [PATCH 66/66] autocancel CI when new commit is made on a pull request (#429) --- .github/workflows/ci-julia-nightly.yml | 8 ++++++++ .github/workflows/ci.yml | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-julia-nightly.yml b/.github/workflows/ci-julia-nightly.yml index 94e3854a6..d018ed30c 100644 --- a/.github/workflows/ci-julia-nightly.yml +++ b/.github/workflows/ci-julia-nightly.yml @@ -4,6 +4,14 @@ on: branches: [master, main] tags: ["*"] pull_request: + +concurrency: + # group by workflow and ref; the last slightly strange component ensures that for pull + # requests, we limit to 1 concurrent job, but for the master branch we don't + group: ${{ github.workflow }}-${{ github.ref }}-${{ (github.ref != 'refs/heads/master' && github.ref != 'refs/heads/main') || github.run_number }} + # Cancel intermediate builds, but only if it is a pull request build. + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + env: PYTHON: ~ jobs: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f7eba665..cf751e1c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: concurrency: # group by workflow and ref; the last slightly strange component ensures that for pull # requests, we limit to 1 concurrent job, but for the master branch we don't - group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref != 'refs/heads/master' || github.run_number }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ (github.ref != 'refs/heads/master' && github.ref != 'refs/heads/main') || github.run_number }} # Cancel intermediate builds, but only if it is a pull request build. cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} @@ -23,7 +23,7 @@ jobs: matrix: version: - '1' - - '1.9' + - '1.10' os: - ubuntu-latest threads: