diff --git a/docs/src/references.bib b/docs/src/references.bib index cb5718796..af49de095 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -824,87 +824,14 @@ @article{Higgott_2023 month=may } -@article{pecorari2025high, - title={High-rate quantum LDPC codes for long-range-connected neutral atom registers}, - author={Pecorari, Laura and Jandura, Sven and Brennen, Gavin K and Pupillo, Guido}, - journal={Nature Communications}, - volume={16}, - number={1}, - pages={1111}, - year={2025}, - publisher={Nature Publishing Group UK London} -} - -@misc{berthusen2025adaptivesyndromeextraction, - title={Adaptive Syndrome Extraction}, - author={Noah Berthusen and Shi Jie Samuel Tan and Eric Huang and Daniel Gottesman}, - year={2025}, - eprint={2502.14835}, - archivePrefix={arXiv}, - primaryClass={quant-ph}, - url={https://arxiv.org/abs/2502.14835}, -} - -@inproceedings{tillich2006minimum, - title={On the minimum distance of structured LDPC codes with two variable nodes of degree 2 per parity-check equation}, - author={Tillich, Jean-Pierre and Z{\'e}mor, Gilles}, - booktitle={2006 IEEE International Symposium on Information Theory}, - pages={1549--1553}, - year={2006}, - organization={IEEE} - url={https://arxiv.org/abs/1108.5738}, -} - -@article{arnault2025upperboundsminimumdistance, - title={Upper Bounds on the Minimum Distance of Structured LDPC Codes}, - author={Arnault, Fran{\c{c}}ois and Gaborit, Philippe and Rozendaal, Wouter and Saussay, Nicolas and Z{\'e}mor, Gilles}, - journal={arXiv preprint arXiv:2501.19125}, - year={2025} -} - -@article{ryan2004introduction, - title={An introduction to LDPC codes}, - author={Ryan, William E and others}, - journal={CRC Handbook for Coding and Signal Processing for Recording Systems}, - volume={5}, - number={2}, - pages={1--23}, - year={2004}, - publisher={CRC Press Boca Raton, FL, USA} -} - -@article{gallager1962ldpc, - author={Gallager, R.}, - journal={IRE Transactions on Information Theory}, - title={Low-density parity-check codes}, - year={1962}, - volume={8}, - number={1}, - pages={21-28}, - keywords={Parity check codes;Maximum likelihood decoding;Equations;Channel capacity;Information theory;Error probability;Linear approximation;Data communication;Error correction codes;Communication systems}, - doi={10.1109/TIT.1962.1057683} -} - -@misc{qubitguide, - author = {Ekert, A. and Hosgood, T. and Kay, A. and Macchiavello, C.}, - title = {Introduction to Quantum Information Science}, - howpublished = {\url{https://qubit.guide}}, - date = {2025-07-24} -} - -@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{postema2025existencecharacterisationbivariatebicycle, - title={Existence and Characterisation of Bivariate Bicycle Codes}, - author={Jasper Johannes Postema and Servaas J. J. M. F. Kokkelmans}, - year={2025}, - eprint={2502.17052}, - archivePrefix={arXiv}, - primaryClass={quant-ph}, - url={https://arxiv.org/abs/2502.17052}, -} +@article{gidney-2021, + author = {Gidney, Craig}, + journal = {Quantum}, + month = {7}, + pages = {497}, + title = {{Stim: a fast stabilizer circuit simulator}}, + volume = {5}, + year = {2021}, + doi = {10.22331/q-2021-07-06-497}, + url = {https://quantum-journal.org/papers/q-2021-07-06-497/}, +} \ No newline at end of file diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index a43f6500f..13c1fbb34 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -10,7 +10,7 @@ module QuantumClifford import LinearAlgebra using LinearAlgebra: inv, mul!, rank, Adjoint, dot, tr import DataStructures -using DataStructures: DefaultDict, Accumulator +using DataStructures: DefaultDict, Accumulator, counter using Combinatorics: combinations using Base.Cartesian using DocStringExtensions @@ -32,7 +32,7 @@ export fastcolumn, fastrow, bitview, quantumstate, tab, rank, BadDataStructure, - affectedqubits, #TODO move to QuantumInterface? + affectedqubits, affectedbits, #TODO move to QuantumInterface? # GF2 stab_to_gf2, gf2_gausselim!, gf2_isinvertible, gf2_invert, gf2_H_to_G, # Canonicalization @@ -63,7 +63,7 @@ export sMX, sMY, sMZ, PauliMeasurement, Reset, sMRX, sMRY, sMRZ, BellMeasurement, ClassicalXOR, VerifyOp, - Register, + Register, BacktrackRegister, # Enumeration and Randoms enumerate_single_qubit_gates, random_clifford1, enumerate_cliffords, symplecticGS, clifford_cardinality, enumerate_phases, @@ -1441,6 +1441,7 @@ include("mctrajectory.jl") include("petrajectory.jl") include("misc_ops.jl") include("classical_register.jl") +include("backtrajectory.jl") include("noise.jl") include("affectedqubits.jl") include("pauli_frames.jl") diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl new file mode 100644 index 000000000..97c9d09f5 --- /dev/null +++ b/src/backtrajectory.jl @@ -0,0 +1,226 @@ +""" +A quantum state representation for efficient simulation using the stabilizer tableau backtracking +method [gidney-2021](@cite). + +This struct stores the inverse of all Clifford operations applied so far, enabling efficient +simulation by working backwards from measurements to the initial |0⟩^⊗n state. By conjugating the +current-time observable Pₓ by the inverse Clifford operation we get some observable from the +start of time that is equivalent to measuring Pₓ at the current time. +""" +struct BacktrackRegister <: AbstractQCState + inv_circuit::CliffordOperator + bits::Vector{Bool} +end + +BacktrackRegister(r::BacktrackRegister) = r +BacktrackRegister(qbits::Int, mbits::Int=0) = BacktrackRegister(one(CliffordOperator, qbits), falses(mbits)) +BacktrackRegister(s::AbstractStabilizer, mbits::Int=0) = BacktrackRegister(inv(CliffordOperator(Destabilizer(s))), falses(mbits)) +BacktrackRegister(r::Register) = BacktrackRegister(quantumstate(r), r.bits) + +Base.copy(r::BacktrackRegister) = BacktrackRegister(copy(r.inv_circuit), copy(r.bits)) +Base.:(==)(l::BacktrackRegister,r::BacktrackRegister) = l.inv_circuit==r.inv_circuit && l.bits==r.bits +Base.hash(r::BacktrackRegister, h::UInt) = hash(r.inv_circuit, hash(r.bits, h)) + +nqubits(r::BacktrackRegister) = nqubits(r.inv_circuit) +bitview(r::BacktrackRegister) = r.bits +quantumstate(r::BacktrackRegister) = apply_inv!(one(Stabilizer, nqubits(r)), r.inv_circuit) +tab(r::BacktrackRegister) = tab(r.inv_circuit) + +tensor(regs::BacktrackRegister...) = BacktrackRegister(tensor([r.inv_circuit for r in regs]), [bit for r in regs for bit in r.bits]) +# tensor(args::AbstractQCState...) = tensor(BacktrackRegister.(args)...) + +function apply!(r::BacktrackRegister, op, args...; kwargs...) + apply_right!(r.inv_circuit, op, args...; kwargs...) + r +end + +function apply!(r::BacktrackRegister, m::sMX) + _, res = projectXrand!(r,m.qubit) + m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) + r +end +function apply!(r::BacktrackRegister, m::sMY) + _, res = projectYrand!(r,m.qubit) + m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) + r +end +function apply!(r::BacktrackRegister, m::sMZ) + _, res = projectZrand!(r,m.qubit) + m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) + r +end +function apply!(r::BacktrackRegister, m::sMRX) + _, res = projectXrand!(r,m.qubit) + m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) + phases(tab(r))[m.qubit] = 0x00 + phases(tab(r))[nqubits(r)+m.qubit] = 0x00 + r +end +function apply!(r::BacktrackRegister, m::sMRY) + _, res = projectYrand!(r,m.qubit) + m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) + if iszero(res) + phases(tab(r))[nqubits(r)+m.qubit] ⊻= 0x02 + end + r +end +function apply!(r::BacktrackRegister, m::sMRZ) + _, res = projectZrand!(r,m.qubit) + m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) + phases(tab(r))[m.qubit] = 0x00 + phases(tab(r))[nqubits(r)+m.qubit] ⊻= 0x02 + r +end +function apply!(r::BacktrackRegister, m::PauliMeasurement) + _, res = projectrand!(r,m.pauli) + m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) + r +end + +function projectXrand!(r::BacktrackRegister, m) + if all(getxrow(tab(r), m) .== 0) + return r, phases(tab(r))[m] + end + + apply!(r, sHadamard(m)) + collapse_z!(r.inv_circuit, m) + apply!(r, sHadamard(m)) + + r, phases(tab(r))[m] +end + +function projectYrand!(r::BacktrackRegister, m) + if all(getxrow(tab(r), m) .== getxrow(tab(r), nqubits(r)+m)) + return r, eval_y_obs(r.inv_circuit, m).phase[] + end + + apply!(r, sHadamardYZ(m)) + collapse_z!(r.inv_circuit, m) + apply!(r, sHadamardYZ(m)) + + r, eval_y_obs(r.inv_circuit, m).phase[] +end + +function projectZrand!(r::BacktrackRegister, m) + if all(getxrow(tab(r), nqubits(r)+m) .== 0) + return r, phases(tab(r))[nqubits(r)+m] + end + + collapse_z!(r.inv_circuit, m) + + r, phases(tab(r))[nqubits(r)+m] +end + +function projectrand!(r::BacktrackRegister, pauli) + if all(iszero.(pauli.xz)) + return pauli.phase[] & 0x02 + end + + h_xz = [] + h_yz = [] + cnot = [] + meas = 0 + + for q in nqubits(pauli) + x, z = pauli[q] + if x + if z + push!(h_yz, q) + else + push!(h_xz, q) + end + end + + if iszero(meas) + meas = q + else + push!(cnot, q) + end + end + @assert meas > 0 + + for q in h_xz + apply!(r, sHadamard(q)) + end + for q in h_yz + apply!(r, sHadamardYZ(q)) + end + for q1 in cnot + apply!(r, sCNOT(q1, meas)) + end + _, res = projectZrand!(r, meas) + for q1 in reverse(cnot) + apply!(r, sCNOT(q1, meas)) + end + for q in reverse(h_yz) + apply!(r, sHadamardYZ(q)) + end + for q in reverse(h_xz) + apply!(r, sHadamard(q)) + end + + r, res +end + +# function traceout!(r::BacktrackRegister, arg) + # TODO +# end + +# function applybranches(r::BacktrackRegister, op) + # TODO +# end + + +function eval_y_obs(c::CliffordOperator, q::Int) + result = c[q] + @assert result.phase[] & 0x01 == 0 + og_result_sign = result.phase[] + mul_right!(result, c[nqubits(c)+q]; phases=Val(true)) + log_i = result.phase[] + 1 + @assert log_i & 0x01 == 0 + if log_i & 2 != 0 + og_result_sign ⊻= 0x02 + end + result.phase[] = og_result_sign + return result +end + +function collapse_z!(c::CliffordOperator, q::Int) + n = nqubits(c) + t = tab(c) + + # Search for any stabilizer generator that anti-commutes with the measurement observable. + pivot = 1 + while pivot <= n && getxbit(t, n+q, pivot) == 0 + pivot += 1 + end + if pivot >= n+1 + # No anti-commuting stabilizer generator. Measurement is deterministic. + return -1 + end + + # Perform partial Gaussian elimination over the stabilizer generators that anti-commute with the measurement. + # Do this by introducing no-effect-because-control-is-zero CNOTs at the beginning of time. + for k in pivot+1:n + if getxbit(t, n+q, k) > 0 + apply!(c, sCNOT(pivot, k); phases=true) + end + end + + # Swap the now-isolated anti-commuting stabilizer generator for one that commutes with the measurement. + if getzbit(t, n+q, pivot) == 0 + apply!(c, sHadamard(pivot); phases=true) + else + apply!(c, sHadamardYZ(pivot); phases=true) + end + + # Assign a measurement result. + if rand(Bool) + apply!(c, sX(pivot); phases=true) + end + + return pivot +end + +@inline getxrow(s::Tableau, r::Int) = s.xzs[1:2:end, r] +@inline getzrow(s::Tableau, r::Int) = s.xzs[2:2:end, r] \ No newline at end of file diff --git a/src/classical_register.jl b/src/classical_register.jl index dbff70d7a..bcdaec947 100644 --- a/src/classical_register.jl +++ b/src/classical_register.jl @@ -12,6 +12,7 @@ Register(s::MixedDestabilizer,nbits::Int) = Register(s, falses(nbits)) Base.copy(r::Register) = Register(copy(r.stab),copy(r.bits)) Base.:(==)(l::Register,r::Register) = l.stab==r.stab && l.bits==r.bits +Base.hash(r::Register, h::UInt) = hash(r.stab, hash(r.bits, h)) stabilizerview(r::Register) = stabilizerview(quantumstate(r)) destabilizerview(r::Register) = destabilizerview(quantumstate(r)) @@ -39,6 +40,10 @@ function apply!(r::Register, op, args...; kwargs...) apply!(quantumstate(r), op, args...; kwargs...) r end +function apply_inv!(r::Register, op, args...; kwargs...) + apply_inv!(quantumstate(r), op, args...; kwargs...) + r +end function apply!(r::Register, m::sMX) _, res = projectXrand!(r,m.qubit) diff --git a/src/mctrajectory.jl b/src/mctrajectory.jl index 2e9b38bf2..60dcd79fc 100644 --- a/src/mctrajectory.jl +++ b/src/mctrajectory.jl @@ -63,8 +63,7 @@ mctrajectories(initialstate,circuit;trajectories=500,keepstates::Bool=false) = _ function _mctrajectories(initialstate,circuit;trajectories=500,keepstates::Val{B}=Val(false)) where {B} if B - counter = DefaultDict{Tuple{typeof(initialstate),CircuitStatus},Int} - counts = counter((mctrajectory!(copy(initialstate),circuit)[2] for i in 1:trajectories)) # TODO use threads or at least a generator + counts = counter((mctrajectory!(copy(initialstate),circuit) for i in 1:trajectories)) # TODO use threads or at least a generator return counts else counts = countmap((mctrajectory!(copy(initialstate),circuit)[2] for i in 1:trajectories)) # TODO use threads or at least a generator diff --git a/src/noise.jl b/src/noise.jl index 226a77780..2ab305863 100644 --- a/src/noise.jl +++ b/src/noise.jl @@ -12,16 +12,6 @@ function applynoise!(state, noise, indices::Base.AbstractVecOrTuple) return state end -# Implementations for Register -function applynoise!(r::Register, n, i::Int) - apply!(quantumstate(r), n, i) - return r -end -function applynoise!(r::Register, n, indices::Base.AbstractVecOrTuple) - apply!(quantumstate(r), n, indices) - return r -end - """Depolarization noise model with total probability of error `p`.""" struct UnbiasedUncorrelatedNoise{T} <: AbstractNoise p::T @@ -47,27 +37,26 @@ function PauliNoise(p) UnbiasedUncorrelatedNoise(p) end -function applynoise!(s::AbstractStabilizer,noise::UnbiasedUncorrelatedNoise,i::Int) +function applynoise!(s::AbstractQCState, noise::UnbiasedUncorrelatedNoise, i::Int) infid = noise.p/3 r = rand() if r