Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fe3ae66
initial commit
sagnikpal2004 Jul 23, 2025
f1f22ab
fixed more gates
sagnikpal2004 Jul 23, 2025
adc4eb2
complete single-qubit
sagnikpal2004 Jul 23, 2025
5b1c6da
updated names
sagnikpal2004 Jul 25, 2025
3a5f82a
complete two-qubit gates
sagnikpal2004 Jul 26, 2025
e3e84d8
initial commit
sagnikpal2004 Jul 24, 2025
2d349a5
minor fixes
sagnikpal2004 Jul 25, 2025
09f9f44
fix collapse_z phase randomness
sagnikpal2004 Jul 25, 2025
9663a0f
collapse_x fixes
sagnikpal2004 Jul 25, 2025
b4cda36
sMY and sMRY
sagnikpal2004 Jul 25, 2025
4abcabc
reset
sagnikpal2004 Jul 25, 2025
4fbcdc9
better method for calcuting number of qubits required
sagnikpal2004 Jul 25, 2025
1bec6dd
naming changes
sagnikpal2004 Jul 25, 2025
c34c6b4
pauli measurements for backtraj
sagnikpal2004 Jul 26, 2025
06cdd26
naming changes
sagnikpal2004 Jul 26, 2025
82d5e8d
remove sSQRTZ, update tests
sagnikpal2004 Jul 26, 2025
0daec50
Merge remote-tracking branch 'origin/apply_right' into backtraj
sagnikpal2004 Jul 26, 2025
f7fda1b
switch to using registers
sagnikpal2004 Jul 27, 2025
848642b
add tests
sagnikpal2004 Jul 28, 2025
21dd0dd
updated function names
sagnikpal2004 Aug 6, 2025
21031c0
fix mctrajectory_keepstates counting issue
sagnikpal2004 Aug 8, 2025
e3a7c62
resets are now measure-reset aliases
sagnikpal2004 Aug 10, 2025
1ea084f
rename to `BacktrackingRegister`
sagnikpal2004 Aug 10, 2025
7c8a89d
set initial state
sagnikpal2004 Aug 11, 2025
26cb1d5
Merge branch 'master' of https://github.com/QuantumSavory/QuantumClif…
sagnikpal2004 Aug 11, 2025
1d44505
implement noise, rename to BacktrackRegister
sagnikpal2004 Nov 25, 2025
c219720
remove redundant resets
sagnikpal2004 Nov 25, 2025
4b00700
tests - not working
sagnikpal2004 Nov 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 11 additions & 84 deletions docs/src/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -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/},
}
7 changes: 4 additions & 3 deletions src/QuantumClifford.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double check that the recent 0.19 does not break this

using Combinatorics: combinations
using Base.Cartesian
using DocStringExtensions
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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")
Expand Down
226 changes: 226 additions & 0 deletions src/backtrajectory.jl
Original file line number Diff line number Diff line change
@@ -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]
Comment on lines +225 to +226
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there elsewhere in the codebase that functions like this are defined, maybe in the linalg file or somewhere near where Tableau is defined -- just double check that there is no better place to put them

5 changes: 5 additions & 0 deletions src/classical_register.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 1 addition & 2 deletions src/mctrajectory.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading