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)