From fe3ae66e58f2d19084674a2b553593c944a6f6b9 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Wed, 23 Jul 2025 15:15:27 +0530 Subject: [PATCH 01/26] initial commit --- src/QuantumClifford.jl | 3 +- src/apply_right.jl | 251 +++++++++++++++++++++++++++++++++++++++ test/test_apply_right.jl | 98 +++++++++++++++ 3 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 src/apply_right.jl create mode 100644 test/test_apply_right.jl diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 1474ea68d..43460dce4 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -40,7 +40,7 @@ export # Linear Algebra tensor, tensor_pow, logdot, expect, - apply!, apply_inv!, + apply!, apply_inv!, apply_right!, permutesystems, permutesystems!, # Low Level Function Interface generate!, project!, reset_qubits!, traceout!, @@ -1460,5 +1460,6 @@ include("grouptableaux.jl") include("plotting_extensions.jl") # include("gpu_adapters.jl") +include("apply_right.jl") end #module diff --git a/src/apply_right.jl b/src/apply_right.jl new file mode 100644 index 000000000..50fb63ea2 --- /dev/null +++ b/src/apply_right.jl @@ -0,0 +1,251 @@ +"""the `apply_right!` function is used to prepend any quantum operation to unitary Clifford operation""" +function apply_right! end + +function apply_right!(l::CliffordOperator, r::AbstractCliffordOperator; phases=true) + @warn "Slow apply_right! operation: $r" + apply!(CliffordOperator(r, nqubits(l)), l; phases=phases) +end + + +############################## +# Single-qubit gates +############################## + +function apply_right!(l::CliffordOperator, r::sHadamard) + rowswap!(tab(l), r.q, nqubits(l)+r.q) + return l +end + +function apply_right!(l::CliffordOperator, r::sHadamardXY) + mul_right!(l[r.q], l[nqubits(l)+r.q]; phases=Val(false)) + apply_right!(l, sY(r.q)) + return l +end + +function apply_right!(l::CliffordOperator, r::sHadamardYZ) + mul_right!(l[nqubits(l)+r.q], l[r.q]; phases=Val(false)) + apply_right!(l, sZ(r.q)) + return l +end + +function apply_right!(l::CliffordOperator, r::sPhase) + mul_right!(l[r.q], l[nqubits(l)+r.q]; phases=Val(true)) + return l +end + +# function apply_right!(l::CliffordOperator, r::sInvPhase) +# return l +# end + +function apply_right!(l::CliffordOperator, r::sX) + phases(tab(l))[nqubits(l)+r.q] ⊻= 0x02 + return l +end + +function apply_right!(l::CliffordOperator, r::sY) + phases(tab(l))[r.q] ⊻= 0x02 + phases(tab(l))[nqubits(l)+r.q] ⊻= 0x02 + return l +end + +function apply_right!(l::CliffordOperator, r::sZ) + phases(tab(l))[r.q] ⊻= 0x02 + return l +end + +function apply_right!(l::CliffordOperator, r::sSQRTX) + apply_right!(l, sInvSQRTX(r.q)) + apply_right!(l, sX(r.q)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvSQRTX) + mul_right!(l[nqubits(l)+r.q], l[r.q]; phases=Val(false)) + return l +end + +function apply_right!(l::CliffordOperator, r::sSQRTY) + phases(tab(l))[nqubits(l)+r.q] ⊻= 0x02 + rowswap!(tab(l), r.q, nqubits(l)+r.q) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvSQRTY) + rowswap!(tab(l), r.q, nqubits(l)+r.q) + phases(tab(l))[nqubits(l)+r.q] ⊻= 0x02 + return l +end + +# function apply_right!(l::CliffordOperator, r::sCXYZ) +# return l +# end + +# function apply_right!(l::CliffordOperator, r::sCZYX) +# return l +# end + +function apply_right!(l::CliffordOperator, r::sId1) + return l +end + + +############################## +# Two-qubit gates +############################## + +function apply_right!(l::CliffordOperator, r::sSWAP) + rowswap!(tab(l), nqubits(l)+r.q1, nqubits(l)+r.q2) + rowswap!(tab(l), r.q1, r.q2) + return l +end + +# function apply_right!(l::CliffordOperator, r::sSWAPCX) +# return l +# end + +# function apply_right!(l::CliffordOperator, r::sInvSWAPCX) +# return l +# end + +function apply_right!(l::CliffordOperator, r::sISWAP) + apply_right!(l, sSWAP(r.q1, r.q2)) + apply_right!(l, sZCZ(r.q1, r.q2)) + apply_right!(l, sSQRTZ(r.q1)) + apply_right!(l, sSQRTZ(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvISWAP) + apply_right!(l, sSWAP(r.q1, r.q2)) + apply_right!(l, sZCZ(r.q1, r.q2)) + apply_right!(l, sInvSQRTZ(r.q1)) + apply_right!(l, sInvSQRTZ(r.q2)) + return l +end + +# function apply_right!(l::CliffordOperator, r::sCZSWAP) +# return l +# end + +# function apply_right!(l::CliffordOperator, r::sCXSWAP) +# return l +# end + +function apply_right!(l::CliffordOperator, r::sCNOT) + return apply_right!(l, sZCX(r.q1, r.q2)) +end + +# function apply_right!(l::CliffordOperator, r::sCPHASE) +# return l +# end + +function apply_right!(l::CliffordOperator, r::sZCX) + mul_right!(l[nqubits(l)+r.q2], l[nqubits(l)+r.q1]; phases=Val(true)) + mul_right!(l[r.q1], l[r.q2]; phases=Val(true)) + return l +end + +function apply_right!(l::CliffordOperator, r::sZCY) + apply_right!(l, sHadamardYZ(r.q2)) + apply_right!(l, sZCZ(r.q1, r.q2)) + apply_right!(l, sHadamardYZ(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sZCZ) + mul_right!(l[r.q2], l[nqubits(l)+r.q1]; phases=Val(true)) + mul_right!(l[r.q1], l[nqubits(l)+r.q2]; phases=Val(true)) + return l +end + +function apply_right!(l::CliffordOperator, r::sXCX) + mul_right!(l[nqubits(l)+r.q2], l[r.q1]; phases=Val(true)) + mul_right!(l[nqubits(l)+r.q1], l[r.q2]; phases=Val(true)) + return l +end + +function apply_right!(l::CliffordOperator, r::sXCY) + apply_right!(l, sHadamardXY(r.q2)) + apply_right!(l, sXCX(r.q1, r.q2)) + apply_right!(l, sHadamardXY(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sXCZ) + return apply_right!(l, sZCX(r.q2, r.q1)) +end + +function apply_right!(l::CliffordOperator, r::sYCX) + return apply_right!(l, sXCY(r.q2, r.q1)) +end + +function apply_right!(l::CliffordOperator, r::sYCY) + apply_right!(l, sHadamardYZ(r.q1)) + apply_right!(l, sHadamardYZ(r.q2)) + apply_right!(l, sZCZ(r.q1, r.q2)) + apply_right!(l, sHadamardYZ(r.q2)) + apply_right!(l, sHadamardYZ(r.q1)) + return l +end + +function apply_right!(l::CliffordOperator, r::sYCZ) + return apply_right!(l, sZCY(r.q2, r.q1)) +end + +# function apply_right!(l::CliffordOperator, r::sZCrY) +# return l +# end + +# function apply_right!(l::CliffordOperator, r::sInvZCrY) +# return l +# end + +function apply_right!(l::CliffordOperator, r::sSQRTZZ) + apply_right!(l, sInvSQRTZZ(r.q1, r.q2)) + apply_right!(l, sZ(r.q1)) + apply_right!(l, sZ(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvSQRTZZ) + mul_right!(l[r.q1], l[nqubits(l)+r.q1]; phases=Val(false)) + mul_right!(l[r.q1], l[nqubits(l)+r.q2]; phases=Val(false)) + mul_right!(l[r.q2], l[nqubits(l)+r.q1]; phases=Val(false)) + mul_right!(l[r.q2], l[nqubits(l)+r.q2]; phases=Val(false)) + return l +end + +function apply_right!(l::CliffordOperator, r::sSQRTXX) + apply_right!(l, sInvSQRTXX(r.q1, r.q2)) + apply_right!(l, sX(r.q1)) + apply_right!(l, sX(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvSQRTXX) + mul_right!(l[nqubits(l)+r.q1], l[r.q1]; phases=Val(false)) + mul_right!(l[nqubits(l)+r.q1], l[r.q2]; phases=Val(false)) + mul_right!(l[nqubits(l)+r.q2], l[r.q1]; phases=Val(false)) + mul_right!(l[nqubits(l)+r.q2], l[r.q2]; phases=Val(false)) + return l +end + +function apply_right!(l::CliffordOperator, r::sSQRTYY) + apply_right!(l, sInvSQRTYY(r.q1, r.q2)) + apply_right!(l, sY(r.q1)) + apply_right!(l, sY(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvSQRTYY) + mul_right!(l[r.q1], l[nqubits(l)+r.q1]; phases=Val(false)) + mul_right!(l[nqubits(l)+r.q1], l[nqubits(l)+r.q2]; phases=Val(false)) + mul_right!(l[nqubits(l)+r.q1], l[r.q2]; phases=Val(false)) + mul_right!(l[r.q2], l[r.q1]; phases=Val(false)) + mul_right!(l[nqubits(l)+r.q2], l[r.q1]; phases=Val(false)) + mul_right!(l[r.q1], l[nqubits(l)+r.q1]; phases=Val(false)) + rowswap!(tab(l), r.q1, nqubits(l)+r.q1) + rowswap!(tab(l), r.q2, nqubits(l)+r.q2) + apply_right!(l, sZ(r.q2)) + return l +end \ No newline at end of file diff --git a/test/test_apply_right.jl b/test/test_apply_right.jl new file mode 100644 index 000000000..f74f22901 --- /dev/null +++ b/test/test_apply_right.jl @@ -0,0 +1,98 @@ +@testitem "Apply Right" begin + using InteractiveUtils + using QuantumClifford: AbstractCliffordOperator + + # SLOW version of apply_right! for testing + function apply_right_slow!(l::CliffordOperator, r::AbstractCliffordOperator; phases=true) + apply!(CliffordOperator(r, nqubits(l)), l; phases=phases) + end + + q = 2 + shots = 1 + + # @testset "Apply Right single-qubit" begin + # for gate in subtypes(AbstractSingleQubitOperator) + # if gate in [sPhase, sInvPhase, SingleQubitOperator, sXYZ, sZYX] + # continue + # end + # r = gate(rand(1:q)) + + # for _ in 1:shots + # l = random_clifford(q) + # @test isequal(apply_right!(copy(l), r), apply_right_slow!(l, r)) + # end + # end + # end + + # @testset "Apply Right two-qubit" begin + # for gate in subtypes(AbstractTwoQubitOperator) + # if gate in [sSWAPCX, sInvSWAPCX, sCZSWAP, sCXSWAP, sCPHASE, sZCrY, sInvZCrY] + # continue + # end + # q1 = rand(1:q); q2 = rand(setdiff(1:q, [q1])) + # r = gate(q1, q2) + + # for _ in 1:shots + # l = random_clifford(q) + # @test isequal(apply_right!(copy(l), r), apply_right_slow!(l, r)) + # end + # end + # end + + @testset "Apply Right single-qubit" begin + for _ in 1:shots + l = random_clifford(q) + q1 = rand(1:q) + + @test isequal(apply_right!(copy(l), sHadamard(q1)), apply_right_slow!(l, sHadamard(q1))) + @test isequal(apply_right!(copy(l), sHadamardXY(q1)), apply_right_slow!(l, sHadamardXY(q1))) + @test isequal(apply_right!(copy(l), sHadamardYZ(q1)), apply_right_slow!(l, sHadamardYZ(q1))) + @test isequal(apply_right!(copy(l), sPhase(q1)), apply_right_slow!(l, sPhase(q1))) + # @test isequal(apply_right!(copy(l), sInvPhase(q1)), apply_right_slow!(l, sInvPhase(q1))) + @test isequal(apply_right!(copy(l), sX(q1)), apply_right_slow!(l, sX(q1))) + @test isequal(apply_right!(copy(l), sY(q1)), apply_right_slow!(l, sY(q1))) + @test isequal(apply_right!(copy(l), sZ(q1)), apply_right_slow!(l, sZ(q1))) + @test isequal(apply_right!(copy(l), sSQRTX(q1)), apply_right_slow!(l, sSQRTX(q1))) + @test isequal(apply_right!(copy(l), sInvSQRTX(q1)), apply_right_slow!(l, sInvSQRTX(q1))) + @test isequal(apply_right!(copy(l), sSQRTY(q1)), apply_right_slow!(l, sSQRTY(q1))) + @test isequal(apply_right!(copy(l), sInvSQRTY(q1)), apply_right_slow!(l, sInvSQRTY(q1))) + # @test isequal(apply_right!(copy(l), sCXYZ(q1)), apply_right_slow!(l, sCXYZ(q1))) + # @test isequal(apply_right!(copy(l), sCZYX(q1)), apply_right_slow!(l, sCZYX(q1))) + @test isequal(apply_right!(copy(l), sId1(q1)), apply_right_slow!(l, sId1(q1))) + end + end + + # @testset "Apply Right two-qubit" begin + # for _ in 1:shots + # l = random_clifford(q) + # q1 = rand(1:q); q2 = rand(setdiff(1:q, [q1])) + + # @test isequal(apply_right!(copy(l), sSWAP(q1, q2)), apply_right_slow!(l, sSWAP(q1, q2))) + # # @test isequal(apply_right!(copy(l), sSWAPCX(q1, q2)), apply_right_slow!(l, sSWAPCX(q1, q2))) + # # @test isequal(apply_right!(copy(l), sInvSWAPCX(q1, q2)), apply_right_slow!(l, sInvSWAPCX(q1, q2))) + # @test isequal(apply_right!(copy(l), sISWAP(q1, q2)), apply_right_slow!(l, sISWAP(q1, q2))) + # @test isequal(apply_right!(copy(l), sInvISWAP(q1, q2)), apply_right_slow!(l, sInvISWAP(q1, q2))) + # # @test isequal(apply_right!(copy(l), sCZSWAP(q1, q2)), apply_right_slow!(l, sCZSWAP(q1, q2))) + # # @test isequal(apply_right!(copy(l), sCXSWAP(q1, q2)), apply_right_slow!(l, sCXSWAP(q1, q2))) + # @test isequal(apply_right!(copy(l), sCNOT(q1, q2)), apply_right_slow!(l, sCNOT(q1, q2))) + # # @test isequal(apply_right!(copy(l), sCPHASE(q1, q2)), apply_right_slow!(l, sCPHASE(q1, q2))) + # @test isequal(apply_right!(copy(l), sZCX(q1, q2)), apply_right_slow!(l, sZCX(q1, q2))) + # @test isequal(apply_right!(copy(l), sZCY(q1, q2)), apply_right_slow!(l, sZCY(q1, q2))) + # @test isequal(apply_right!(copy(l), sZCZ(q1, q2)), apply_right_slow!(l, sZCZ(q1, q2))) + # @test isequal(apply_right!(copy(l), sXCX(q1, q2)), apply_right_slow!(l, sXCX(q1, q2))) + # @test isequal(apply_right!(copy(l), sXCY(q1, q2)), apply_right_slow!(l, sXCY(q1, q2))) + # @test isequal(apply_right!(copy(l), sXCZ(q1, q2)), apply_right_slow!(l, sXCZ(q1, q2))) + # @test isequal(apply_right!(copy(l), sYCX(q1, q2)), apply_right_slow!(l, sYCX(q1, q2))) + # @test isequal(apply_right!(copy(l), sYCY(q1, q2)), apply_right_slow!(l, sYCY(q1, q2))) + # @test isequal(apply_right!(copy(l), sYCZ(q1, q2)), apply_right_slow!(l, sYCZ(q1, q2))) + # # @test isequal(apply_right!(copy(l), sZCrY(q1, q2)), apply_right_slow!(l, sZCrY(q1, q2))) + # # @test isequal(apply_right!(copy(l), sInvZCrY(q1, q2)), apply_right_slow!(l, sInvZCrY(q1, q2))) + # @test isequal(apply_right!(copy(l), sSQRTZZ(q1, q2)), apply_right_slow!(l, sSQRTZZ(q1, q2))) + # @test isequal(apply_right!(copy(l), sInvSQRTZZ(q1, q2)), apply_right_slow!(l, sInvSQRTZZ(q1, q2))) + # @test isequal(apply_right!(copy(l), sSQRTXX(q1, q2)), apply_right_slow!(l, sSQRTXX(q1, q2))) + # @test isequal(apply_right!(copy(l), sInvSQRTXX(q1, q2)), apply_right_slow!(l, sInvSQRTXX(q1, q2))) + # @test isequal(apply_right!(copy(l), sSQRTYY(q1, q2)), apply_right_slow!(l, sSQRTYY(q1, q2))) + # @test isequal(apply_right!(copy(l), sInvSQRTYY(q1, q2)), apply_right_slow!(l, sInvSQRTYY(q1, q2))) + # end + # end +end \ No newline at end of file From f1f22abcf587f515d3ef3b907cebdbf8924ca8ad Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Wed, 23 Jul 2025 17:39:18 +0530 Subject: [PATCH 02/26] fixed more gates --- src/apply_right.jl | 84 ++++++++++++++++++++++++++-------------- test/test_apply_right.jl | 74 ++++++++++++++++++----------------- 2 files changed, 94 insertions(+), 64 deletions(-) diff --git a/src/apply_right.jl b/src/apply_right.jl index 50fb63ea2..7dfd19d27 100644 --- a/src/apply_right.jl +++ b/src/apply_right.jl @@ -6,6 +6,24 @@ function apply_right!(l::CliffordOperator, r::AbstractCliffordOperator; phases=t apply!(CliffordOperator(r, nqubits(l)), l; phases=phases) end +# helper +function mul_right_log_i!(l::PauliOperator, r::PauliOperator) + x = mul_right!(l, r; phases=Val(true)) + x.phase[] = x.phase[] & 0x02 + return x +end + +# new symbolics +"""A \"symbolic\" single-qubit SQRTZ. See also : [`SingleQubitOperator`](@ref), [`AbstractSymbolicOperator`](@ref)""" +struct sSQRTZ <: AbstractSingleQubitOperator + q::Int + sSQRTZ(q) = if q<0 throw(NoZeroQubit) else new(q) end +end +"""A \"symbolic\" single-qubit InvSQRTZ. See also : [`SingleQubitOperator`](@ref), [`AbstractSymbolicOperator`](@ref)""" +struct sInvSQRTZ <: AbstractSingleQubitOperator + q::Int + sInvSQRTZ(q) = if q<0 throw(NoZeroQubit) else new(q) end +end ############################## # Single-qubit gates @@ -17,21 +35,20 @@ function apply_right!(l::CliffordOperator, r::sHadamard) end function apply_right!(l::CliffordOperator, r::sHadamardXY) - mul_right!(l[r.q], l[nqubits(l)+r.q]; phases=Val(false)) + l[r.q] = mul_right_log_i!(l[r.q], l[nqubits(l)+r.q]) apply_right!(l, sY(r.q)) return l end function apply_right!(l::CliffordOperator, r::sHadamardYZ) - mul_right!(l[nqubits(l)+r.q], l[r.q]; phases=Val(false)) + l[nqubits(l)+r.q] = mul_right_log_i!(l[nqubits(l)+r.q], l[r.q]) apply_right!(l, sZ(r.q)) return l end -function apply_right!(l::CliffordOperator, r::sPhase) - mul_right!(l[r.q], l[nqubits(l)+r.q]; phases=Val(true)) - return l -end +# function apply_right!(l::CliffordOperator, r::sPhase) +# return l +# end # function apply_right!(l::CliffordOperator, r::sInvPhase) # return l @@ -60,7 +77,7 @@ function apply_right!(l::CliffordOperator, r::sSQRTX) end function apply_right!(l::CliffordOperator, r::sInvSQRTX) - mul_right!(l[nqubits(l)+r.q], l[r.q]; phases=Val(false)) + l[nqubits(l)+r.q] = mul_right_log_i!(l[nqubits(l)+r.q], l[r.q]) return l end @@ -76,6 +93,17 @@ function apply_right!(l::CliffordOperator, r::sInvSQRTY) return l end +function apply_right!(l::CliffordOperator, r::sSQRTZ) + apply_right!(l, sInvSQRTZ(r.q)) + apply_right!(l, sZ(r.q)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvSQRTZ) + l[r.q] = mul_right_log_i!(l[r.q], l[nqubits(l)+r.q]) + return l +end + # function apply_right!(l::CliffordOperator, r::sCXYZ) # return l # end @@ -84,7 +112,7 @@ end # return l # end -function apply_right!(l::CliffordOperator, r::sId1) +function apply_right!(l::CliffordOperator, ::sId1) return l end @@ -140,8 +168,8 @@ end # end function apply_right!(l::CliffordOperator, r::sZCX) - mul_right!(l[nqubits(l)+r.q2], l[nqubits(l)+r.q1]; phases=Val(true)) - mul_right!(l[r.q1], l[r.q2]; phases=Val(true)) + l[nqubits(l)+r.q2] = mul_right!(l[nqubits(l)+r.q2], l[nqubits(l)+r.q1]; phases=Val(true)) + l[r.q1] = mul_right!(l[r.q1], l[r.q2]; phases=Val(true)) return l end @@ -153,14 +181,14 @@ function apply_right!(l::CliffordOperator, r::sZCY) end function apply_right!(l::CliffordOperator, r::sZCZ) - mul_right!(l[r.q2], l[nqubits(l)+r.q1]; phases=Val(true)) - mul_right!(l[r.q1], l[nqubits(l)+r.q2]; phases=Val(true)) + l[r.q2] = mul_right!(l[r.q2], l[nqubits(l)+r.q1]; phases=Val(true)) + l[r.q1] = mul_right!(l[r.q1], l[nqubits(l)+r.q2]; phases=Val(true)) return l end function apply_right!(l::CliffordOperator, r::sXCX) - mul_right!(l[nqubits(l)+r.q2], l[r.q1]; phases=Val(true)) - mul_right!(l[nqubits(l)+r.q1], l[r.q2]; phases=Val(true)) + l[nqubits(l)+r.q2] = mul_right!(l[nqubits(l)+r.q2], l[r.q1]; phases=Val(true)) + l[nqubits(l)+r.q1] = mul_right!(l[nqubits(l)+r.q1], l[r.q2]; phases=Val(true)) return l end @@ -208,10 +236,10 @@ function apply_right!(l::CliffordOperator, r::sSQRTZZ) end function apply_right!(l::CliffordOperator, r::sInvSQRTZZ) - mul_right!(l[r.q1], l[nqubits(l)+r.q1]; phases=Val(false)) - mul_right!(l[r.q1], l[nqubits(l)+r.q2]; phases=Val(false)) - mul_right!(l[r.q2], l[nqubits(l)+r.q1]; phases=Val(false)) - mul_right!(l[r.q2], l[nqubits(l)+r.q2]; phases=Val(false)) + l[r.q1] = mul_right_log_i!(l[r.q1], l[nqubits(l)+r.q1]) + l[r.q1] = mul_right_log_i!(l[r.q1], l[nqubits(l)+r.q2]) + l[r.q2] = mul_right_log_i!(l[r.q2], l[nqubits(l)+r.q1]) + l[r.q2] = mul_right_log_i!(l[r.q2], l[nqubits(l)+r.q2]) return l end @@ -223,10 +251,10 @@ function apply_right!(l::CliffordOperator, r::sSQRTXX) end function apply_right!(l::CliffordOperator, r::sInvSQRTXX) - mul_right!(l[nqubits(l)+r.q1], l[r.q1]; phases=Val(false)) - mul_right!(l[nqubits(l)+r.q1], l[r.q2]; phases=Val(false)) - mul_right!(l[nqubits(l)+r.q2], l[r.q1]; phases=Val(false)) - mul_right!(l[nqubits(l)+r.q2], l[r.q2]; phases=Val(false)) + l[nqubits(l)+r.q1] = mul_right_log_i!(l[nqubits(l)+r.q1], l[r.q1]) + l[nqubits(l)+r.q1] = mul_right_log_i!(l[nqubits(l)+r.q1], l[r.q2]) + l[nqubits(l)+r.q2] = mul_right_log_i!(l[nqubits(l)+r.q2], l[r.q1]) + l[nqubits(l)+r.q2] = mul_right_log_i!(l[nqubits(l)+r.q2], l[r.q2]) return l end @@ -238,12 +266,12 @@ function apply_right!(l::CliffordOperator, r::sSQRTYY) end function apply_right!(l::CliffordOperator, r::sInvSQRTYY) - mul_right!(l[r.q1], l[nqubits(l)+r.q1]; phases=Val(false)) - mul_right!(l[nqubits(l)+r.q1], l[nqubits(l)+r.q2]; phases=Val(false)) - mul_right!(l[nqubits(l)+r.q1], l[r.q2]; phases=Val(false)) - mul_right!(l[r.q2], l[r.q1]; phases=Val(false)) - mul_right!(l[nqubits(l)+r.q2], l[r.q1]; phases=Val(false)) - mul_right!(l[r.q1], l[nqubits(l)+r.q1]; phases=Val(false)) + l[r.q1] = mul_right_log_i!(l[r.q1], l[nqubits(l)+r.q1]) + l[nqubits(l)+r.q1] = mul_right_log_i!(l[nqubits(l)+r.q1], l[nqubits(l)+r.q2]) + l[nqubits(l)+r.q1] = mul_right_log_i!(l[nqubits(l)+r.q1], l[r.q2]) + l[r.q2] = mul_right_log_i!(l[r.q2], l[r.q1]) + l[nqubits(l)+r.q2] = mul_right_log_i!(l[nqubits(l)+r.q2], l[r.q1]) + l[r.q1] = mul_right_log_i!(l[r.q1], l[nqubits(l)+r.q1]) rowswap!(tab(l), r.q1, nqubits(l)+r.q1) rowswap!(tab(l), r.q2, nqubits(l)+r.q2) apply_right!(l, sZ(r.q2)) diff --git a/test/test_apply_right.jl b/test/test_apply_right.jl index f74f22901..dd1e528d9 100644 --- a/test/test_apply_right.jl +++ b/test/test_apply_right.jl @@ -7,12 +7,12 @@ apply!(CliffordOperator(r, nqubits(l)), l; phases=phases) end - q = 2 - shots = 1 + q = 64 + shots = 16 # @testset "Apply Right single-qubit" begin # for gate in subtypes(AbstractSingleQubitOperator) - # if gate in [sPhase, sInvPhase, SingleQubitOperator, sXYZ, sZYX] + # if gate in [sPhase, sInvPhase, SingleQubitOperator, sCXYZ, sCZYX] # continue # end # r = gate(rand(1:q)) @@ -47,7 +47,7 @@ @test isequal(apply_right!(copy(l), sHadamard(q1)), apply_right_slow!(l, sHadamard(q1))) @test isequal(apply_right!(copy(l), sHadamardXY(q1)), apply_right_slow!(l, sHadamardXY(q1))) @test isequal(apply_right!(copy(l), sHadamardYZ(q1)), apply_right_slow!(l, sHadamardYZ(q1))) - @test isequal(apply_right!(copy(l), sPhase(q1)), apply_right_slow!(l, sPhase(q1))) + # @test isequal(apply_right!(copy(l), sPhase(q1)), apply_right_slow!(l, sPhase(q1))) # @test isequal(apply_right!(copy(l), sInvPhase(q1)), apply_right_slow!(l, sInvPhase(q1))) @test isequal(apply_right!(copy(l), sX(q1)), apply_right_slow!(l, sX(q1))) @test isequal(apply_right!(copy(l), sY(q1)), apply_right_slow!(l, sY(q1))) @@ -56,43 +56,45 @@ @test isequal(apply_right!(copy(l), sInvSQRTX(q1)), apply_right_slow!(l, sInvSQRTX(q1))) @test isequal(apply_right!(copy(l), sSQRTY(q1)), apply_right_slow!(l, sSQRTY(q1))) @test isequal(apply_right!(copy(l), sInvSQRTY(q1)), apply_right_slow!(l, sInvSQRTY(q1))) + # @test isequal(apply_right!(copy(l), sSQRTZ(q1)), apply_right_slow!(l, sSQRTZ(q1))) + # @test isequal(apply_right!(copy(l), sInvSQRTZ(q1)), apply_right_slow!(l, sInvSQRTZ(q1))) # @test isequal(apply_right!(copy(l), sCXYZ(q1)), apply_right_slow!(l, sCXYZ(q1))) # @test isequal(apply_right!(copy(l), sCZYX(q1)), apply_right_slow!(l, sCZYX(q1))) @test isequal(apply_right!(copy(l), sId1(q1)), apply_right_slow!(l, sId1(q1))) end end - # @testset "Apply Right two-qubit" begin - # for _ in 1:shots - # l = random_clifford(q) - # q1 = rand(1:q); q2 = rand(setdiff(1:q, [q1])) + @testset "Apply Right two-qubit" begin + for _ in 1:shots + l = random_clifford(q) + q1 = rand(1:q); q2 = rand(setdiff(1:q, [q1])) - # @test isequal(apply_right!(copy(l), sSWAP(q1, q2)), apply_right_slow!(l, sSWAP(q1, q2))) - # # @test isequal(apply_right!(copy(l), sSWAPCX(q1, q2)), apply_right_slow!(l, sSWAPCX(q1, q2))) - # # @test isequal(apply_right!(copy(l), sInvSWAPCX(q1, q2)), apply_right_slow!(l, sInvSWAPCX(q1, q2))) - # @test isequal(apply_right!(copy(l), sISWAP(q1, q2)), apply_right_slow!(l, sISWAP(q1, q2))) - # @test isequal(apply_right!(copy(l), sInvISWAP(q1, q2)), apply_right_slow!(l, sInvISWAP(q1, q2))) - # # @test isequal(apply_right!(copy(l), sCZSWAP(q1, q2)), apply_right_slow!(l, sCZSWAP(q1, q2))) - # # @test isequal(apply_right!(copy(l), sCXSWAP(q1, q2)), apply_right_slow!(l, sCXSWAP(q1, q2))) - # @test isequal(apply_right!(copy(l), sCNOT(q1, q2)), apply_right_slow!(l, sCNOT(q1, q2))) - # # @test isequal(apply_right!(copy(l), sCPHASE(q1, q2)), apply_right_slow!(l, sCPHASE(q1, q2))) - # @test isequal(apply_right!(copy(l), sZCX(q1, q2)), apply_right_slow!(l, sZCX(q1, q2))) - # @test isequal(apply_right!(copy(l), sZCY(q1, q2)), apply_right_slow!(l, sZCY(q1, q2))) - # @test isequal(apply_right!(copy(l), sZCZ(q1, q2)), apply_right_slow!(l, sZCZ(q1, q2))) - # @test isequal(apply_right!(copy(l), sXCX(q1, q2)), apply_right_slow!(l, sXCX(q1, q2))) - # @test isequal(apply_right!(copy(l), sXCY(q1, q2)), apply_right_slow!(l, sXCY(q1, q2))) - # @test isequal(apply_right!(copy(l), sXCZ(q1, q2)), apply_right_slow!(l, sXCZ(q1, q2))) - # @test isequal(apply_right!(copy(l), sYCX(q1, q2)), apply_right_slow!(l, sYCX(q1, q2))) - # @test isequal(apply_right!(copy(l), sYCY(q1, q2)), apply_right_slow!(l, sYCY(q1, q2))) - # @test isequal(apply_right!(copy(l), sYCZ(q1, q2)), apply_right_slow!(l, sYCZ(q1, q2))) - # # @test isequal(apply_right!(copy(l), sZCrY(q1, q2)), apply_right_slow!(l, sZCrY(q1, q2))) - # # @test isequal(apply_right!(copy(l), sInvZCrY(q1, q2)), apply_right_slow!(l, sInvZCrY(q1, q2))) - # @test isequal(apply_right!(copy(l), sSQRTZZ(q1, q2)), apply_right_slow!(l, sSQRTZZ(q1, q2))) - # @test isequal(apply_right!(copy(l), sInvSQRTZZ(q1, q2)), apply_right_slow!(l, sInvSQRTZZ(q1, q2))) - # @test isequal(apply_right!(copy(l), sSQRTXX(q1, q2)), apply_right_slow!(l, sSQRTXX(q1, q2))) - # @test isequal(apply_right!(copy(l), sInvSQRTXX(q1, q2)), apply_right_slow!(l, sInvSQRTXX(q1, q2))) - # @test isequal(apply_right!(copy(l), sSQRTYY(q1, q2)), apply_right_slow!(l, sSQRTYY(q1, q2))) - # @test isequal(apply_right!(copy(l), sInvSQRTYY(q1, q2)), apply_right_slow!(l, sInvSQRTYY(q1, q2))) - # end - # end + @test isequal(apply_right!(copy(l), sSWAP(q1, q2)), apply_right_slow!(l, sSWAP(q1, q2))) + # @test isequal(apply_right!(copy(l), sSWAPCX(q1, q2)), apply_right_slow!(l, sSWAPCX(q1, q2))) + # @test isequal(apply_right!(copy(l), sInvSWAPCX(q1, q2)), apply_right_slow!(l, sInvSWAPCX(q1, q2))) + @test isequal(apply_right!(copy(l), sISWAP(q1, q2)), apply_right_slow!(l, sISWAP(q1, q2))) + @test isequal(apply_right!(copy(l), sInvISWAP(q1, q2)), apply_right_slow!(l, sInvISWAP(q1, q2))) + # @test isequal(apply_right!(copy(l), sCZSWAP(q1, q2)), apply_right_slow!(l, sCZSWAP(q1, q2))) + # @test isequal(apply_right!(copy(l), sCXSWAP(q1, q2)), apply_right_slow!(l, sCXSWAP(q1, q2))) + @test isequal(apply_right!(copy(l), sCNOT(q1, q2)), apply_right_slow!(l, sCNOT(q1, q2))) + # @test isequal(apply_right!(copy(l), sCPHASE(q1, q2)), apply_right_slow!(l, sCPHASE(q1, q2))) + @test isequal(apply_right!(copy(l), sZCX(q1, q2)), apply_right_slow!(l, sZCX(q1, q2))) + @test isequal(apply_right!(copy(l), sZCY(q1, q2)), apply_right_slow!(l, sZCY(q1, q2))) + @test isequal(apply_right!(copy(l), sZCZ(q1, q2)), apply_right_slow!(l, sZCZ(q1, q2))) + @test isequal(apply_right!(copy(l), sXCX(q1, q2)), apply_right_slow!(l, sXCX(q1, q2))) + @test isequal(apply_right!(copy(l), sXCY(q1, q2)), apply_right_slow!(l, sXCY(q1, q2))) + @test isequal(apply_right!(copy(l), sXCZ(q1, q2)), apply_right_slow!(l, sXCZ(q1, q2))) + @test isequal(apply_right!(copy(l), sYCX(q1, q2)), apply_right_slow!(l, sYCX(q1, q2))) + @test isequal(apply_right!(copy(l), sYCY(q1, q2)), apply_right_slow!(l, sYCY(q1, q2))) + @test isequal(apply_right!(copy(l), sYCZ(q1, q2)), apply_right_slow!(l, sYCZ(q1, q2))) + # @test isequal(apply_right!(copy(l), sZCrY(q1, q2)), apply_right_slow!(l, sZCrY(q1, q2))) + # @test isequal(apply_right!(copy(l), sInvZCrY(q1, q2)), apply_right_slow!(l, sInvZCrY(q1, q2))) + @test isequal(apply_right!(copy(l), sSQRTZZ(q1, q2)), apply_right_slow!(l, sSQRTZZ(q1, q2))) + @test isequal(apply_right!(copy(l), sInvSQRTZZ(q1, q2)), apply_right_slow!(l, sInvSQRTZZ(q1, q2))) + @test isequal(apply_right!(copy(l), sSQRTXX(q1, q2)), apply_right_slow!(l, sSQRTXX(q1, q2))) + @test isequal(apply_right!(copy(l), sInvSQRTXX(q1, q2)), apply_right_slow!(l, sInvSQRTXX(q1, q2))) + @test isequal(apply_right!(copy(l), sSQRTYY(q1, q2)), apply_right_slow!(l, sSQRTYY(q1, q2))) + @test isequal(apply_right!(copy(l), sInvSQRTYY(q1, q2)), apply_right_slow!(l, sInvSQRTYY(q1, q2))) + end + end end \ No newline at end of file From adc4eb20f045d4fedc157722142d39811217de41 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Wed, 23 Jul 2025 19:12:24 +0530 Subject: [PATCH 03/26] complete single-qubit --- src/QuantumClifford.jl | 2 +- src/apply_right.jl | 33 +++++++++++++++++++++------------ test/test_apply_right.jl | 8 ++++---- test/test_inv.jl | 2 +- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 43460dce4..d11d99720 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -53,7 +53,7 @@ export # Symbolic Clifford Ops AbstractSymbolicOperator, AbstractSingleQubitOperator, AbstractTwoQubitOperator, sHadamard, sPhase, sInvPhase, SingleQubitOperator, sId1, sX, sY, sZ, - sHadamardXY, sHadamardYZ, sSQRTX, sInvSQRTX, sSQRTY, sInvSQRTY, sCXYZ, sCZYX, + sHadamardXY, sHadamardYZ, sSQRTX, sInvSQRTX, sSQRTY, sInvSQRTY, sSQRTZ, sInvSQRTZ, sCXYZ, sCZYX, sCNOT, sCPHASE, sSWAP, sXCX, sXCY, sXCZ, sYCX, sYCY, sYCZ, sZCX, sZCY, sZCZ, sZCrY, sInvZCrY, sSWAPCX, sInvSWAPCX, sCZSWAP, sCXSWAP, sISWAP, sInvISWAP, diff --git a/src/apply_right.jl b/src/apply_right.jl index 7dfd19d27..76e96f2a7 100644 --- a/src/apply_right.jl +++ b/src/apply_right.jl @@ -46,13 +46,16 @@ function apply_right!(l::CliffordOperator, r::sHadamardYZ) return l end -# function apply_right!(l::CliffordOperator, r::sPhase) -# return l -# end +function apply_right!(l::CliffordOperator, r::sPhase) + l[r.q] = mul_right_log_i!(l[r.q], l[nqubits(l)+r.q]) + apply_right!(l, sZ(r.q)) + return l +end -# function apply_right!(l::CliffordOperator, r::sInvPhase) -# return l -# end +function apply_right!(l::CliffordOperator, r::sInvPhase) + l[r.q] = mul_right_log_i!(l[r.q], l[nqubits(l)+r.q]) + return l +end function apply_right!(l::CliffordOperator, r::sX) phases(tab(l))[nqubits(l)+r.q] ⊻= 0x02 @@ -104,13 +107,18 @@ function apply_right!(l::CliffordOperator, r::sInvSQRTZ) return l end -# function apply_right!(l::CliffordOperator, r::sCXYZ) -# return l -# end +function apply_right!(l::CliffordOperator, r::sCXYZ) + rowswap!(tab(l), r.q, nqubits(l)+r.q) + l[r.q] = mul_right_log_i!(l[r.q], l[nqubits(l)+r.q]) + return l +end -# function apply_right!(l::CliffordOperator, r::sCZYX) -# return l -# end +function apply_right!(l::CliffordOperator, r::sCZYX) + rowswap!(tab(l), r.q, nqubits(l)+r.q) + l[nqubits(l)+r.q] = mul_right_log_i!(l[nqubits(l)+r.q], l[r.q]) + apply_right!(l, sX(r.q)) + return l +end function apply_right!(l::CliffordOperator, ::sId1) return l @@ -128,6 +136,7 @@ function apply_right!(l::CliffordOperator, r::sSWAP) end # function apply_right!(l::CliffordOperator, r::sSWAPCX) +# rowswap!(tab(l), r.q1, r.q2) # return l # end diff --git a/test/test_apply_right.jl b/test/test_apply_right.jl index dd1e528d9..411a5e965 100644 --- a/test/test_apply_right.jl +++ b/test/test_apply_right.jl @@ -47,8 +47,8 @@ @test isequal(apply_right!(copy(l), sHadamard(q1)), apply_right_slow!(l, sHadamard(q1))) @test isequal(apply_right!(copy(l), sHadamardXY(q1)), apply_right_slow!(l, sHadamardXY(q1))) @test isequal(apply_right!(copy(l), sHadamardYZ(q1)), apply_right_slow!(l, sHadamardYZ(q1))) - # @test isequal(apply_right!(copy(l), sPhase(q1)), apply_right_slow!(l, sPhase(q1))) - # @test isequal(apply_right!(copy(l), sInvPhase(q1)), apply_right_slow!(l, sInvPhase(q1))) + @test isequal(apply_right!(copy(l), sPhase(q1)), apply_right_slow!(l, sPhase(q1))) + @test isequal(apply_right!(copy(l), sInvPhase(q1)), apply_right_slow!(l, sInvPhase(q1))) @test isequal(apply_right!(copy(l), sX(q1)), apply_right_slow!(l, sX(q1))) @test isequal(apply_right!(copy(l), sY(q1)), apply_right_slow!(l, sY(q1))) @test isequal(apply_right!(copy(l), sZ(q1)), apply_right_slow!(l, sZ(q1))) @@ -58,8 +58,8 @@ @test isequal(apply_right!(copy(l), sInvSQRTY(q1)), apply_right_slow!(l, sInvSQRTY(q1))) # @test isequal(apply_right!(copy(l), sSQRTZ(q1)), apply_right_slow!(l, sSQRTZ(q1))) # @test isequal(apply_right!(copy(l), sInvSQRTZ(q1)), apply_right_slow!(l, sInvSQRTZ(q1))) - # @test isequal(apply_right!(copy(l), sCXYZ(q1)), apply_right_slow!(l, sCXYZ(q1))) - # @test isequal(apply_right!(copy(l), sCZYX(q1)), apply_right_slow!(l, sCZYX(q1))) + @test isequal(apply_right!(copy(l), sCXYZ(q1)), apply_right_slow!(l, sCXYZ(q1))) + @test isequal(apply_right!(copy(l), sCZYX(q1)), apply_right_slow!(l, sCZYX(q1))) @test isequal(apply_right!(copy(l), sId1(q1)), apply_right_slow!(l, sId1(q1))) end end diff --git a/test/test_inv.jl b/test/test_inv.jl index 20e047f73..8e8752bb5 100644 --- a/test/test_inv.jl +++ b/test/test_inv.jl @@ -5,7 +5,7 @@ stabilizers = [S" I", S" X", S" Y", S" Z", S"-I", S"-X", S"-Y", S"-Z",] for gate in subtypes(AbstractSingleQubitOperator) - gate == SingleQubitOperator && continue + gate ∈ [SingleQubitOperator, sSQRTZ, sInvSQRTZ] && continue for stab in stabilizers @test apply_inv!(stab, gate(1)) == apply!(stab, inv(CliffordOperator(gate(1), 1))) end From 5b1c6dafc7dd1dee48b145bcb97ecd1cda3ddfec Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Sat, 26 Jul 2025 02:23:35 +0530 Subject: [PATCH 04/26] updated names --- src/apply_right.jl | 71 ++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/src/apply_right.jl b/src/apply_right.jl index 76e96f2a7..0091c609d 100644 --- a/src/apply_right.jl +++ b/src/apply_right.jl @@ -7,23 +7,12 @@ function apply_right!(l::CliffordOperator, r::AbstractCliffordOperator; phases=t end # helper -function mul_right_log_i!(l::PauliOperator, r::PauliOperator) +function mul_right_ignore_anticommute!(l::PauliOperator, r::PauliOperator) x = mul_right!(l, r; phases=Val(true)) x.phase[] = x.phase[] & 0x02 return x end -# new symbolics -"""A \"symbolic\" single-qubit SQRTZ. See also : [`SingleQubitOperator`](@ref), [`AbstractSymbolicOperator`](@ref)""" -struct sSQRTZ <: AbstractSingleQubitOperator - q::Int - sSQRTZ(q) = if q<0 throw(NoZeroQubit) else new(q) end -end -"""A \"symbolic\" single-qubit InvSQRTZ. See also : [`SingleQubitOperator`](@ref), [`AbstractSymbolicOperator`](@ref)""" -struct sInvSQRTZ <: AbstractSingleQubitOperator - q::Int - sInvSQRTZ(q) = if q<0 throw(NoZeroQubit) else new(q) end -end ############################## # Single-qubit gates @@ -35,25 +24,25 @@ function apply_right!(l::CliffordOperator, r::sHadamard) end function apply_right!(l::CliffordOperator, r::sHadamardXY) - l[r.q] = mul_right_log_i!(l[r.q], l[nqubits(l)+r.q]) + l[r.q] = mul_right_ignore_anticommute!(l[r.q], l[nqubits(l)+r.q]) apply_right!(l, sY(r.q)) return l end function apply_right!(l::CliffordOperator, r::sHadamardYZ) - l[nqubits(l)+r.q] = mul_right_log_i!(l[nqubits(l)+r.q], l[r.q]) + l[nqubits(l)+r.q] = mul_right_ignore_anticommute!(l[nqubits(l)+r.q], l[r.q]) apply_right!(l, sZ(r.q)) return l end function apply_right!(l::CliffordOperator, r::sPhase) - l[r.q] = mul_right_log_i!(l[r.q], l[nqubits(l)+r.q]) + l[r.q] = mul_right_ignore_anticommute!(l[r.q], l[nqubits(l)+r.q]) apply_right!(l, sZ(r.q)) return l end function apply_right!(l::CliffordOperator, r::sInvPhase) - l[r.q] = mul_right_log_i!(l[r.q], l[nqubits(l)+r.q]) + l[r.q] = mul_right_ignore_anticommute!(l[r.q], l[nqubits(l)+r.q]) return l end @@ -80,7 +69,7 @@ function apply_right!(l::CliffordOperator, r::sSQRTX) end function apply_right!(l::CliffordOperator, r::sInvSQRTX) - l[nqubits(l)+r.q] = mul_right_log_i!(l[nqubits(l)+r.q], l[r.q]) + l[nqubits(l)+r.q] = mul_right_ignore_anticommute!(l[nqubits(l)+r.q], l[r.q]) return l end @@ -96,26 +85,26 @@ function apply_right!(l::CliffordOperator, r::sInvSQRTY) return l end -function apply_right!(l::CliffordOperator, r::sSQRTZ) - apply_right!(l, sInvSQRTZ(r.q)) +function apply_right!(l::CliffordOperator, r::sPhase) + apply_right!(l, sInvPhase(r.q)) apply_right!(l, sZ(r.q)) return l end -function apply_right!(l::CliffordOperator, r::sInvSQRTZ) - l[r.q] = mul_right_log_i!(l[r.q], l[nqubits(l)+r.q]) +function apply_right!(l::CliffordOperator, r::sInvPhase) + l[r.q] = mul_right_ignore_anticommute!(l[r.q], l[nqubits(l)+r.q]) return l end function apply_right!(l::CliffordOperator, r::sCXYZ) rowswap!(tab(l), r.q, nqubits(l)+r.q) - l[r.q] = mul_right_log_i!(l[r.q], l[nqubits(l)+r.q]) + l[r.q] = mul_right_ignore_anticommute!(l[r.q], l[nqubits(l)+r.q]) return l end function apply_right!(l::CliffordOperator, r::sCZYX) rowswap!(tab(l), r.q, nqubits(l)+r.q) - l[nqubits(l)+r.q] = mul_right_log_i!(l[nqubits(l)+r.q], l[r.q]) + l[nqubits(l)+r.q] = mul_right_ignore_anticommute!(l[nqubits(l)+r.q], l[r.q]) apply_right!(l, sX(r.q)) return l end @@ -147,16 +136,16 @@ end function apply_right!(l::CliffordOperator, r::sISWAP) apply_right!(l, sSWAP(r.q1, r.q2)) apply_right!(l, sZCZ(r.q1, r.q2)) - apply_right!(l, sSQRTZ(r.q1)) - apply_right!(l, sSQRTZ(r.q2)) + apply_right!(l, sPhase(r.q1)) + apply_right!(l, sPhase(r.q2)) return l end function apply_right!(l::CliffordOperator, r::sInvISWAP) apply_right!(l, sSWAP(r.q1, r.q2)) apply_right!(l, sZCZ(r.q1, r.q2)) - apply_right!(l, sInvSQRTZ(r.q1)) - apply_right!(l, sInvSQRTZ(r.q2)) + apply_right!(l, sInvPhase(r.q1)) + apply_right!(l, sInvPhase(r.q2)) return l end @@ -245,10 +234,10 @@ function apply_right!(l::CliffordOperator, r::sSQRTZZ) end function apply_right!(l::CliffordOperator, r::sInvSQRTZZ) - l[r.q1] = mul_right_log_i!(l[r.q1], l[nqubits(l)+r.q1]) - l[r.q1] = mul_right_log_i!(l[r.q1], l[nqubits(l)+r.q2]) - l[r.q2] = mul_right_log_i!(l[r.q2], l[nqubits(l)+r.q1]) - l[r.q2] = mul_right_log_i!(l[r.q2], l[nqubits(l)+r.q2]) + l[r.q1] = mul_right_ignore_anticommute!(l[r.q1], l[nqubits(l)+r.q1]) + l[r.q1] = mul_right_ignore_anticommute!(l[r.q1], l[nqubits(l)+r.q2]) + l[r.q2] = mul_right_ignore_anticommute!(l[r.q2], l[nqubits(l)+r.q1]) + l[r.q2] = mul_right_ignore_anticommute!(l[r.q2], l[nqubits(l)+r.q2]) return l end @@ -260,10 +249,10 @@ function apply_right!(l::CliffordOperator, r::sSQRTXX) end function apply_right!(l::CliffordOperator, r::sInvSQRTXX) - l[nqubits(l)+r.q1] = mul_right_log_i!(l[nqubits(l)+r.q1], l[r.q1]) - l[nqubits(l)+r.q1] = mul_right_log_i!(l[nqubits(l)+r.q1], l[r.q2]) - l[nqubits(l)+r.q2] = mul_right_log_i!(l[nqubits(l)+r.q2], l[r.q1]) - l[nqubits(l)+r.q2] = mul_right_log_i!(l[nqubits(l)+r.q2], l[r.q2]) + l[nqubits(l)+r.q1] = mul_right_ignore_anticommute!(l[nqubits(l)+r.q1], l[r.q1]) + l[nqubits(l)+r.q1] = mul_right_ignore_anticommute!(l[nqubits(l)+r.q1], l[r.q2]) + l[nqubits(l)+r.q2] = mul_right_ignore_anticommute!(l[nqubits(l)+r.q2], l[r.q1]) + l[nqubits(l)+r.q2] = mul_right_ignore_anticommute!(l[nqubits(l)+r.q2], l[r.q2]) return l end @@ -275,12 +264,12 @@ function apply_right!(l::CliffordOperator, r::sSQRTYY) end function apply_right!(l::CliffordOperator, r::sInvSQRTYY) - l[r.q1] = mul_right_log_i!(l[r.q1], l[nqubits(l)+r.q1]) - l[nqubits(l)+r.q1] = mul_right_log_i!(l[nqubits(l)+r.q1], l[nqubits(l)+r.q2]) - l[nqubits(l)+r.q1] = mul_right_log_i!(l[nqubits(l)+r.q1], l[r.q2]) - l[r.q2] = mul_right_log_i!(l[r.q2], l[r.q1]) - l[nqubits(l)+r.q2] = mul_right_log_i!(l[nqubits(l)+r.q2], l[r.q1]) - l[r.q1] = mul_right_log_i!(l[r.q1], l[nqubits(l)+r.q1]) + l[r.q1] = mul_right_ignore_anticommute!(l[r.q1], l[nqubits(l)+r.q1]) + l[nqubits(l)+r.q1] = mul_right_ignore_anticommute!(l[nqubits(l)+r.q1], l[nqubits(l)+r.q2]) + l[nqubits(l)+r.q1] = mul_right_ignore_anticommute!(l[nqubits(l)+r.q1], l[r.q2]) + l[r.q2] = mul_right_ignore_anticommute!(l[r.q2], l[r.q1]) + l[nqubits(l)+r.q2] = mul_right_ignore_anticommute!(l[nqubits(l)+r.q2], l[r.q1]) + l[r.q1] = mul_right_ignore_anticommute!(l[r.q1], l[nqubits(l)+r.q1]) rowswap!(tab(l), r.q1, nqubits(l)+r.q1) rowswap!(tab(l), r.q2, nqubits(l)+r.q2) apply_right!(l, sZ(r.q2)) From 3a5f82a432043a4126dc8f96c6c003df25b5dbbf Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Sat, 26 Jul 2025 06:57:58 +0530 Subject: [PATCH 05/26] complete two-qubit gates --- src/apply_right.jl | 128 +++++++++++++++++++++++++++------------ test/test_apply_right.jl | 45 +++----------- 2 files changed, 95 insertions(+), 78 deletions(-) diff --git a/src/apply_right.jl b/src/apply_right.jl index 0091c609d..929d2764e 100644 --- a/src/apply_right.jl +++ b/src/apply_right.jl @@ -1,12 +1,54 @@ -"""the `apply_right!` function is used to prepend any quantum operation to unitary Clifford operation""" +"""the `apply_right!` function is used to right multiply any quantum operation to unitary +Clifford operation or Pauli product""" function apply_right! end -function apply_right!(l::CliffordOperator, r::AbstractCliffordOperator; phases=true) - @warn "Slow apply_right! operation: $r" - apply!(CliffordOperator(r, nqubits(l)), l; phases=phases) -end -# helper +# dense_cliffords + +function apply_right!(l::CliffordOperator, r::PauliOperator; phases=false) + nqubits(l)==nqubits(r) || throw(DimensionMismatch("The Clifford and Pauli operators need to act on the same number of qubits.")) + tab(l).phases[nqubits(l)+1:end] .⊻= r[1:nqubits(l)] + tab(l).phases[1:nqubits(l)] .⊻= r[nqubits(l)+1:end] +end + +function apply_right!(l::CliffordOperator, r::CliffordOperator; phases=true) + nqubits(l)==nqubits(r) || throw(DimensionMismatch("The two Clifford operators need to act on the same number of qubits.")) + l_tab = tab(l) + r_tab = tab(r) + threadlocal = l.buffer + new_xzs = Vector{typeof(threadlocal)}(undef, length(l_tab)) + @inbounds for row_r in eachindex(r_tab) + zero!(threadlocal) + apply_right_row_kernel!(threadlocal, row_r, l_tab, r_tab, phases=phases) + new_xzs[row_r] = copy(threadlocal) + end + @inbounds for row_l in eachindex(l_tab) + l_tab[row_l] = new_xzs[row_l] + end + l +end + +@inline function apply_right_row_kernel!(new_lrow, row, l_tab, r_tab; phases=true) + phases && (new_lrow.phase[] = r_tab.phases[row]) + n = nqubits(l_tab) + for qubit in 1:n + x,z = r_tab[row,qubit] + if phases&&x&&z + new_lrow.phase[] -= 0x1 + end + if x + mul_left!(new_lrow, l_tab, qubit, phases=Val(phases)) + end + if z + mul_left!(new_lrow, l_tab, qubit+n, phases=Val(phases)) + end + end + new_lrow +end + + +# symbolic_cliffords + function mul_right_ignore_anticommute!(l::PauliOperator, r::PauliOperator) x = mul_right!(l, r; phases=Val(true)) x.phase[] = x.phase[] & 0x02 @@ -36,7 +78,7 @@ function apply_right!(l::CliffordOperator, r::sHadamardYZ) end function apply_right!(l::CliffordOperator, r::sPhase) - l[r.q] = mul_right_ignore_anticommute!(l[r.q], l[nqubits(l)+r.q]) + apply_right!(l, sInvPhase(r.q)) apply_right!(l, sZ(r.q)) return l end @@ -85,17 +127,6 @@ function apply_right!(l::CliffordOperator, r::sInvSQRTY) return l end -function apply_right!(l::CliffordOperator, r::sPhase) - apply_right!(l, sInvPhase(r.q)) - apply_right!(l, sZ(r.q)) - return l -end - -function apply_right!(l::CliffordOperator, r::sInvPhase) - l[r.q] = mul_right_ignore_anticommute!(l[r.q], l[nqubits(l)+r.q]) - return l -end - function apply_right!(l::CliffordOperator, r::sCXYZ) rowswap!(tab(l), r.q, nqubits(l)+r.q) l[r.q] = mul_right_ignore_anticommute!(l[r.q], l[nqubits(l)+r.q]) @@ -124,14 +155,17 @@ function apply_right!(l::CliffordOperator, r::sSWAP) return l end -# function apply_right!(l::CliffordOperator, r::sSWAPCX) -# rowswap!(tab(l), r.q1, r.q2) -# return l -# end +function apply_right!(l::CliffordOperator, r::sSWAPCX) + apply_right!(l, sSWAP(r.q1, r.q2)) + apply_right!(l, sCNOT(r.q2, r.q1)) + return l +end -# function apply_right!(l::CliffordOperator, r::sInvSWAPCX) -# return l -# end +function apply_right!(l::CliffordOperator, r::sInvSWAPCX) + apply_right!(l, sCNOT(r.q2, r.q1)) + apply_right!(l, sSWAP(r.q1, r.q2)) + return l +end function apply_right!(l::CliffordOperator, r::sISWAP) apply_right!(l, sSWAP(r.q1, r.q2)) @@ -149,21 +183,26 @@ function apply_right!(l::CliffordOperator, r::sInvISWAP) return l end -# function apply_right!(l::CliffordOperator, r::sCZSWAP) -# return l -# end +function apply_right!(l::CliffordOperator, r::sCZSWAP) + apply_right!(l, sZCZ(r.q2, r.q1)) + apply_right!(l, sSWAP(r.q1, r.q2)) + return l +end -# function apply_right!(l::CliffordOperator, r::sCXSWAP) -# return l -# end +function apply_right!(l::CliffordOperator, r::sCXSWAP) + apply_right!(l, sCNOT(r.q2, r.q1)) + apply_right!(l, sSWAP(r.q1, r.q2)) + return l +end function apply_right!(l::CliffordOperator, r::sCNOT) return apply_right!(l, sZCX(r.q1, r.q2)) end -# function apply_right!(l::CliffordOperator, r::sCPHASE) -# return l -# end +function apply_right!(l::CliffordOperator, r::sCPHASE) + apply_right!(l, sZCZ(r.q1, r.q2)) + return l +end function apply_right!(l::CliffordOperator, r::sZCX) l[nqubits(l)+r.q2] = mul_right!(l[nqubits(l)+r.q2], l[nqubits(l)+r.q1]; phases=Val(true)) @@ -218,13 +257,22 @@ function apply_right!(l::CliffordOperator, r::sYCZ) return apply_right!(l, sZCY(r.q2, r.q1)) end -# function apply_right!(l::CliffordOperator, r::sZCrY) -# return l -# end +function apply_right!(l::CliffordOperator, r::sZCrY) + l[r.q1] = mul_left!(l[r.q1], l[r.q2]) + l[r.q2] = mul_left!(l[r.q2], l[nqubits(l)+r.q1]) + l[nqubits(l)+r.q2] = mul_left!(l[nqubits(l)+r.q2], l[nqubits(l)+r.q1]) + l[r.q1] = mul_left!(l[r.q1], l[nqubits(l)+r.q2]) + return l +end -# function apply_right!(l::CliffordOperator, r::sInvZCrY) -# return l -# end +function apply_right!(l::CliffordOperator, r::sInvZCrY) + l[r.q1] = mul_left!(l[r.q1], l[r.q2]) + l[r.q2] = mul_left!(l[r.q2], l[nqubits(l)+r.q1]) + l[nqubits(l)+r.q2] = mul_left!(l[nqubits(l)+r.q2], l[nqubits(l)+r.q1]) + l[r.q1] = mul_left!(l[r.q1], l[nqubits(l)+r.q2]) + phases(tab(l))[r.q1] ⊻= 0x02 + return l +end function apply_right!(l::CliffordOperator, r::sSQRTZZ) apply_right!(l, sInvSQRTZZ(r.q1, r.q2)) diff --git a/test/test_apply_right.jl b/test/test_apply_right.jl index 411a5e965..5cf1185a3 100644 --- a/test/test_apply_right.jl +++ b/test/test_apply_right.jl @@ -10,35 +10,6 @@ q = 64 shots = 16 - # @testset "Apply Right single-qubit" begin - # for gate in subtypes(AbstractSingleQubitOperator) - # if gate in [sPhase, sInvPhase, SingleQubitOperator, sCXYZ, sCZYX] - # continue - # end - # r = gate(rand(1:q)) - - # for _ in 1:shots - # l = random_clifford(q) - # @test isequal(apply_right!(copy(l), r), apply_right_slow!(l, r)) - # end - # end - # end - - # @testset "Apply Right two-qubit" begin - # for gate in subtypes(AbstractTwoQubitOperator) - # if gate in [sSWAPCX, sInvSWAPCX, sCZSWAP, sCXSWAP, sCPHASE, sZCrY, sInvZCrY] - # continue - # end - # q1 = rand(1:q); q2 = rand(setdiff(1:q, [q1])) - # r = gate(q1, q2) - - # for _ in 1:shots - # l = random_clifford(q) - # @test isequal(apply_right!(copy(l), r), apply_right_slow!(l, r)) - # end - # end - # end - @testset "Apply Right single-qubit" begin for _ in 1:shots l = random_clifford(q) @@ -56,8 +27,6 @@ @test isequal(apply_right!(copy(l), sInvSQRTX(q1)), apply_right_slow!(l, sInvSQRTX(q1))) @test isequal(apply_right!(copy(l), sSQRTY(q1)), apply_right_slow!(l, sSQRTY(q1))) @test isequal(apply_right!(copy(l), sInvSQRTY(q1)), apply_right_slow!(l, sInvSQRTY(q1))) - # @test isequal(apply_right!(copy(l), sSQRTZ(q1)), apply_right_slow!(l, sSQRTZ(q1))) - # @test isequal(apply_right!(copy(l), sInvSQRTZ(q1)), apply_right_slow!(l, sInvSQRTZ(q1))) @test isequal(apply_right!(copy(l), sCXYZ(q1)), apply_right_slow!(l, sCXYZ(q1))) @test isequal(apply_right!(copy(l), sCZYX(q1)), apply_right_slow!(l, sCZYX(q1))) @test isequal(apply_right!(copy(l), sId1(q1)), apply_right_slow!(l, sId1(q1))) @@ -70,14 +39,14 @@ q1 = rand(1:q); q2 = rand(setdiff(1:q, [q1])) @test isequal(apply_right!(copy(l), sSWAP(q1, q2)), apply_right_slow!(l, sSWAP(q1, q2))) - # @test isequal(apply_right!(copy(l), sSWAPCX(q1, q2)), apply_right_slow!(l, sSWAPCX(q1, q2))) - # @test isequal(apply_right!(copy(l), sInvSWAPCX(q1, q2)), apply_right_slow!(l, sInvSWAPCX(q1, q2))) + @test isequal(apply_right!(copy(l), sSWAPCX(q1, q2)), apply_right_slow!(l, sSWAPCX(q1, q2))) + @test isequal(apply_right!(copy(l), sInvSWAPCX(q1, q2)), apply_right_slow!(l, sInvSWAPCX(q1, q2))) @test isequal(apply_right!(copy(l), sISWAP(q1, q2)), apply_right_slow!(l, sISWAP(q1, q2))) @test isequal(apply_right!(copy(l), sInvISWAP(q1, q2)), apply_right_slow!(l, sInvISWAP(q1, q2))) - # @test isequal(apply_right!(copy(l), sCZSWAP(q1, q2)), apply_right_slow!(l, sCZSWAP(q1, q2))) - # @test isequal(apply_right!(copy(l), sCXSWAP(q1, q2)), apply_right_slow!(l, sCXSWAP(q1, q2))) + @test isequal(apply_right!(copy(l), sCZSWAP(q1, q2)), apply_right_slow!(l, sCZSWAP(q1, q2))) + @test isequal(apply_right!(copy(l), sCXSWAP(q1, q2)), apply_right_slow!(l, sCXSWAP(q1, q2))) @test isequal(apply_right!(copy(l), sCNOT(q1, q2)), apply_right_slow!(l, sCNOT(q1, q2))) - # @test isequal(apply_right!(copy(l), sCPHASE(q1, q2)), apply_right_slow!(l, sCPHASE(q1, q2))) + @test isequal(apply_right!(copy(l), sCPHASE(q1, q2)), apply_right_slow!(l, sCPHASE(q1, q2))) @test isequal(apply_right!(copy(l), sZCX(q1, q2)), apply_right_slow!(l, sZCX(q1, q2))) @test isequal(apply_right!(copy(l), sZCY(q1, q2)), apply_right_slow!(l, sZCY(q1, q2))) @test isequal(apply_right!(copy(l), sZCZ(q1, q2)), apply_right_slow!(l, sZCZ(q1, q2))) @@ -87,8 +56,8 @@ @test isequal(apply_right!(copy(l), sYCX(q1, q2)), apply_right_slow!(l, sYCX(q1, q2))) @test isequal(apply_right!(copy(l), sYCY(q1, q2)), apply_right_slow!(l, sYCY(q1, q2))) @test isequal(apply_right!(copy(l), sYCZ(q1, q2)), apply_right_slow!(l, sYCZ(q1, q2))) - # @test isequal(apply_right!(copy(l), sZCrY(q1, q2)), apply_right_slow!(l, sZCrY(q1, q2))) - # @test isequal(apply_right!(copy(l), sInvZCrY(q1, q2)), apply_right_slow!(l, sInvZCrY(q1, q2))) + @test isequal(apply_right!(copy(l), sZCrY(q1, q2)), apply_right_slow!(l, sZCrY(q1, q2))) + @test isequal(apply_right!(copy(l), sInvZCrY(q1, q2)), apply_right_slow!(l, sInvZCrY(q1, q2))) @test isequal(apply_right!(copy(l), sSQRTZZ(q1, q2)), apply_right_slow!(l, sSQRTZZ(q1, q2))) @test isequal(apply_right!(copy(l), sInvSQRTZZ(q1, q2)), apply_right_slow!(l, sInvSQRTZZ(q1, q2))) @test isequal(apply_right!(copy(l), sSQRTXX(q1, q2)), apply_right_slow!(l, sSQRTXX(q1, q2))) From e3e84d8185584547f25f468b97637bd4ecc0ec52 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Fri, 25 Jul 2025 04:38:33 +0530 Subject: [PATCH 06/26] initial commit --- src/QuantumClifford.jl | 3 + src/backtrajectory.jl | 152 +++++++++++++++++++++++++++++++++++++++++ test/test_backtraj.jl | 4 ++ 3 files changed, 159 insertions(+) create mode 100644 src/backtrajectory.jl create mode 100644 test/test_backtraj.jl diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index d11d99720..148abd2fe 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -91,6 +91,8 @@ export mctrajectory!, mctrajectories, applywstatus!, # petrajectories petrajectories, applybranches, + # backtrajectory + backtrajectory, # nonclifford GeneralizedStabilizer, UnitaryPauliChannel, PauliChannel, pcT, pcPhase, # makie plotting -- defined only when extension is loaded @@ -1435,6 +1437,7 @@ include("linalg.jl") include("operator_traits.jl") include("mctrajectory.jl") include("petrajectory.jl") +include("backtrajectory.jl") include("misc_ops.jl") include("classical_register.jl") include("noise.jl") diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl new file mode 100644 index 000000000..1532332cb --- /dev/null +++ b/src/backtrajectory.jl @@ -0,0 +1,152 @@ +""" +Simulates measurement results of a Clifford circuit acting on an `n`-qubit |0⟩^⊗n state using the stabilizer tableau backtracking method, +as described by Gidney (2021). + +This method incrementally folds operations into an identity tableau by prepending inverses of Clifford gates. Pauli-Z measurements are +resolved by transforming their observables to the initial state; deterministic measurements are directly computed from tableau signs, +while random measurements are simplified and simulated with randomized gate insertions. + +Reference: +Gidney, C. (2021). Stim: A fast stabilizer circuit simulator. *Quantum*, 5, 497. https://doi.org/10.22331/q-2021-07-06-497 +""" +function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int) + T = one(CliffordOperator, n) + results = Int8[] + + for op in circuit + if op isa AbstractCliffordOperator + apply_right!(T, op) + elseif op isa sMX + push!(results, do_MX!(T, op)) + # elseif op isa sMY + # push!(results, do_MY!(T, op)) + elseif op isa sMZ + push!(results, do_MZ!(T, op)) + else + error("Unsupported operation: $(typeof(op))") + end + end + + return results +end + + +# function do_MY!(T, op::sMY) +# collapse_y!(T, op.qubit) +# return eval_y_obs(T, q) +# end + +# function collapse_y!(T, q::Int) +# if is_deterministic_y(T, q) +# return +# end + +# apply!(T, sHadamardYZ(q); phases=true) +# collapse_z!(T, q) +# apply!(T, sHadamardYZ(q); phases=true) +# end + +# function eval_y_obs(T, q::Int) +# result = T[q] +# log_i = mul_right!(result, T[nqubits(T)+q]) +# log_i += 1 +# @assert log_i & 1 == 0 +# if log_i & 2 +# result.phase[] +# end +# return result +# end + +function do_MX!(T, op::sMX) + collapse_x!(T, op.qubit) + return phases(tab(T))[op.qubit] == 0x00 ? 1 : -1 +end + +# function do_MRX!(T, op::sMRX) +# collapse_x!(T, op.qubit) +# result = phases(tab(T))[op.qubit] == 0x00 ? 1 : -1 +# # change the signs to zero +# return result +# end + +function collapse_x!(T, q::Int) + if is_deterministic_x(T, q) + return + end + + apply!(T, sHadamard(q); phases=true) + collapse_z!(T, q) + apply!(T, sHadamard(q); phases=true) +end + +function do_MZ!(T, op::sMZ) + collapse_z!(T, op.qubit) + return phases(tab(T))[op.qubit+nqubits(T)] == 0x00 ? 1 : -1 +end + +function collapse_z!(T, q::Int) + if is_deterministic_z(T, q) + return + end + + n = nqubits(T) + t = tab(T) + + # 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!(T, 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!(T, sHadamard(pivot); phases=true) + else + apply!(T, sHadamardYZ(pivot); phases=true) + end + + return pivot +end + +@inline is_deterministic_x(T, q::Int) = all(getxbytes(T, q) .== 0) +@inline is_deterministic_y(T, q::Int) = all(getxbytes(T, q) .== getxbytes(T, nqubits(T)+q)) +@inline is_deterministic_z(T, q::Int) = all(getxbytes(T, nqubits(T)+q) .== 0) + +@inline getxbytes(T, r) = tab(T).xzs[1:2:end,r] +@inline getzbytes(T, r) = tab(T).xzs[2:2:end,r] + + +# function backtrajectory(circuit0::Vector{AbstractOperation}, state::AbstractStabilizer) +# # TODO - Figure out if to use Reset or Gates +# pushfirst!(circuit0, Reset(state, 1:nqubits(state))) +# return backtrajectory(circuit0, nqubits(state)) +# end + +function backtrajectory(circuit::Vector{AbstractOperation}) + n = 0 + for op in circuit + if op isa AbstractSingleQubitOperator + n = max(n, op.q) + elseif op isa AbstractTwoQubitOperator + n = max(n, op.q1, op.q2) + elseif op isa AbstractMeasurement + n = max(n, op.qubit) + else + error("Unsupported operation: $(typeof(op))") + end + end + + return backtrajectory(circuit, n) +end diff --git a/test/test_backtraj.jl b/test/test_backtraj.jl new file mode 100644 index 000000000..7e6da9070 --- /dev/null +++ b/test/test_backtraj.jl @@ -0,0 +1,4 @@ +@testitem "test backtrajectory" begin + @testset "test backtrajectory" begin + end +end \ No newline at end of file From 2d349a533b4b2ecd5b2a49643c47e27a23cd1ca6 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Fri, 25 Jul 2025 16:30:00 +0530 Subject: [PATCH 07/26] minor fixes --- src/backtrajectory.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index 1532332cb..04141220e 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -97,7 +97,7 @@ function collapse_z!(T, q::Int) while pivot <= n && getxbit(t, n+q, pivot) == 0 pivot += 1 end - if pivot == n+1 + if pivot >= n+1 # No anti-commuting stabilizer generator. Measurement is deterministic. return -1 end @@ -134,7 +134,7 @@ end # return backtrajectory(circuit0, nqubits(state)) # end -function backtrajectory(circuit::Vector{AbstractOperation}) +function backtrajectory(circuit::Vector{<:AbstractOperation}) n = 0 for op in circuit if op isa AbstractSingleQubitOperator From 09f9f44a2ed57401062bde8c37eb242f17da80c9 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Fri, 25 Jul 2025 16:33:48 +0530 Subject: [PATCH 08/26] fix collapse_z phase randomness --- src/backtrajectory.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index 04141220e..e3c192f71 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -117,6 +117,11 @@ function collapse_z!(T, q::Int) apply!(T, sHadamardYZ(pivot); phases=true) end + # Assign a measurement result. + if rand(Bool) #TODO: maybe support a RNG + apply!(T, sX(pivot); phases=true) + end + return pivot end From 9663a0f01d1bfd61b0233604dc76ac14247fa78b Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Fri, 25 Jul 2025 19:04:34 +0530 Subject: [PATCH 09/26] collapse_x fixes --- src/backtrajectory.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index e3c192f71..300c73088 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -74,9 +74,9 @@ function collapse_x!(T, q::Int) return end - apply!(T, sHadamard(q); phases=true) + apply_right!(T, sHadamard(q)) collapse_z!(T, q) - apply!(T, sHadamard(q); phases=true) + apply_right!(T, sHadamard(q)) end function do_MZ!(T, op::sMZ) From b4cda36b82f2b539632c6144bc51568bab601571 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Sat, 26 Jul 2025 00:29:54 +0530 Subject: [PATCH 10/26] sMY and sMRY --- src/backtrajectory.jl | 101 +++++++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 36 deletions(-) diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index 300c73088..8b5e47f7c 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -18,10 +18,16 @@ function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int) apply_right!(T, op) elseif op isa sMX push!(results, do_MX!(T, op)) - # elseif op isa sMY - # push!(results, do_MY!(T, op)) + elseif op isa sMY + push!(results, do_MY!(T, op)) elseif op isa sMZ push!(results, do_MZ!(T, op)) + elseif op isa sMRX + push!(results, do_MRX!(T, op)) + elseif op isa sMRY + push!(results, do_MRY!(T, op)) + elseif op isa sMRZ + push!(results, do_MRZ!(T, op)) else error("Unsupported operation: $(typeof(op))") end @@ -31,57 +37,78 @@ function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int) end -# function do_MY!(T, op::sMY) -# collapse_y!(T, op.qubit) -# return eval_y_obs(T, q) -# end - -# function collapse_y!(T, q::Int) -# if is_deterministic_y(T, q) -# return -# end - -# apply!(T, sHadamardYZ(q); phases=true) -# collapse_z!(T, q) -# apply!(T, sHadamardYZ(q); phases=true) -# end - -# function eval_y_obs(T, q::Int) -# result = T[q] -# log_i = mul_right!(result, T[nqubits(T)+q]) -# log_i += 1 -# @assert log_i & 1 == 0 -# if log_i & 2 -# result.phase[] -# end -# return result -# end - function do_MX!(T, op::sMX) collapse_x!(T, op.qubit) return phases(tab(T))[op.qubit] == 0x00 ? 1 : -1 end -# function do_MRX!(T, op::sMRX) -# collapse_x!(T, op.qubit) -# result = phases(tab(T))[op.qubit] == 0x00 ? 1 : -1 -# # change the signs to zero -# return result -# end +function do_MRX!(T, op::sMRX) + collapse_x!(T, op.qubit) + result = phases(tab(T))[op.qubit] == 0x00 ? 1 : -1 + phases(tab(T))[op.qubit] = 0x00 + phases(tab(T))[nqubits(T)+op.qubit] = 0x00 + return result +end function collapse_x!(T, q::Int) if is_deterministic_x(T, q) return end - + apply_right!(T, sHadamard(q)) collapse_z!(T, q) apply_right!(T, sHadamard(q)) end +function do_MY!(T, op::sMY) + collapse_y!(T, op.qubit) + return eval_y_obs(T, op.qubit).phase[] == 0x00 ? 1 : -1 +end + +function do_MRY!(T, op::sMRY) + collapse_y!(T, op.qubit) + result = eval_y_obs(T, op.qubit).phase[] == 0x00 ? 1 : -1 + if result == -1 + phases(tab(T))[nqubits(T)+op.qubit] ⊻= 0x02 + end + return result +end + +function collapse_y!(T, q::Int) + if is_deterministic_y(T, q) + return + end + + apply_right!(T, sHadamardYZ(q)) + collapse_z!(T, q) + apply_right!(T, sHadamardYZ(q)) +end + +function eval_y_obs(T, q::Int) + result = T[q] + @assert result.phase[] & 0x01 == 0 + og_result_sign = result.phase[] + mul_right!(result, T[nqubits(T)+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 do_MZ!(T, op::sMZ) collapse_z!(T, op.qubit) - return phases(tab(T))[op.qubit+nqubits(T)] == 0x00 ? 1 : -1 + return phases(tab(T))[nqubits(T)+op.qubit] == 0x00 ? 1 : -1 +end + +function do_MRZ!(T, op::sMRZ) + collapse_z!(T, op.qubit) + result = phases(tab(T))[nqubits(T)+op.qubit] == 0x00 ? 1 : -1 + phases(tab(T))[op.qubit] = 0x00 + phases(tab(T))[nqubits(T)+op.qubit] = 0x00 + return result end function collapse_z!(T, q::Int) @@ -148,6 +175,8 @@ function backtrajectory(circuit::Vector{<:AbstractOperation}) n = max(n, op.q1, op.q2) elseif op isa AbstractMeasurement n = max(n, op.qubit) + elseif typeof(op) in [sMRX, sMRY, sMRZ] + n = max(n, op.qubit) else error("Unsupported operation: $(typeof(op))") end From 4abcabcbb5443efca76a96384ff4449c5a2b449d Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Sat, 26 Jul 2025 01:02:31 +0530 Subject: [PATCH 11/26] reset --- src/QuantumClifford.jl | 3 ++- src/backtrajectory.jl | 51 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 148abd2fe..e0505305b 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -60,7 +60,8 @@ export sSQRTZZ, sInvSQRTZZ, sSQRTXX, sInvSQRTXX, sSQRTYY, sInvSQRTYY, # Misc Ops SparseGate, - sMX, sMY, sMZ, PauliMeasurement, Reset, sMRX, sMRY, sMRZ, + sMX, sMY, sMZ, PauliMeasurement, + Reset, sMRX, sMRY, sMRZ, sRX, sRY, sRZ, BellMeasurement, ClassicalXOR, VerifyOp, Register, diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index 8b5e47f7c..7cdb614bc 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -1,3 +1,31 @@ +# new symbolics +abstract type AbstractReset <: AbstractOperation end + +"""Reset a qubit to the |+⟩ state. + +See also: [`sMRX`](@ref), [`Reset`](@ref)""" +struct sRX <: AbstractReset + qubit::Int + sRX(q) = if q<=0 throw(NoZeroQubit) else new(q) end +end + +"""Reset a qubit to the |i₊⟩ state. + +See also: [`sMRY`](@ref), [`Reset`](@ref)""" +struct sRY <: AbstractReset + qubit::Int + sRY(q) = if q<=0 throw(NoZeroQubit) else new(q) end +end + +"""Reset a qubit to the |0⟩ state. + +See also: [`sMRZ`](@ref), [`Reset`](@ref)""" +struct sRZ <: AbstractReset + qubit::Int + sRZ(q) = if q<=0 throw(NoZeroQubit) else new(q) end +end + + """ Simulates measurement results of a Clifford circuit acting on an `n`-qubit |0⟩^⊗n state using the stabilizer tableau backtracking method, as described by Gidney (2021). @@ -28,6 +56,8 @@ function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int) push!(results, do_MRY!(T, op)) elseif op isa sMRZ push!(results, do_MRZ!(T, op)) + elseif op isa AbstractReset + do_reset!(T, op) else error("Unsupported operation: $(typeof(op))") end @@ -50,6 +80,12 @@ function do_MRX!(T, op::sMRX) return result end +function do_reset!(T, op::sRX) + collapse_x!(T, op.qubit) + phases(tab(T))[op.qubit] = 0x00 + phases(tab(T))[nqubits(T)+op.qubit] = 0x00 +end + function collapse_x!(T, q::Int) if is_deterministic_x(T, q) return @@ -74,6 +110,13 @@ function do_MRY!(T, op::sMRY) return result end +function do_reset!(T, op::sRY) + collapse_y!(T, op.qubit) + if eval_y_obs(T, op.qubit).phase[] != 0x00 + phases(tab(T))[nqubits(T)+op.qubit] ⊻= 0x02 + end +end + function collapse_y!(T, q::Int) if is_deterministic_y(T, q) return @@ -111,6 +154,12 @@ function do_MRZ!(T, op::sMRZ) return result end +function do_reset!(T, op::sRZ) + collapse_z!(T, op.qubit) + phases(tab(T))[op.qubit] = 0x00 + phases(tab(T))[nqubits(T)+op.qubit] = 0x00 +end + function collapse_z!(T, q::Int) if is_deterministic_z(T, q) return @@ -175,6 +224,8 @@ function backtrajectory(circuit::Vector{<:AbstractOperation}) n = max(n, op.q1, op.q2) elseif op isa AbstractMeasurement n = max(n, op.qubit) + elseif op isa AbstractReset + n = max(n, op.qubit) elseif typeof(op) in [sMRX, sMRY, sMRZ] n = max(n, op.qubit) else From 4fbcdc9e5ff8e954d947e95358c79ad54b9cbf77 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Sat, 26 Jul 2025 01:17:43 +0530 Subject: [PATCH 12/26] better method for calcuting number of qubits required --- src/affectedqubits.jl | 1 + src/backtrajectory.jl | 39 ++++++++++++++++++++++----------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/affectedqubits.jl b/src/affectedqubits.jl index 3609710e3..4d3214d92 100644 --- a/src/affectedqubits.jl +++ b/src/affectedqubits.jl @@ -10,6 +10,7 @@ affectedqubits(n::NoiseOp) = n.indices affectedqubits(g::PauliMeasurement) = 1:length(g.pauli) affectedqubits(p::PauliOperator) = 1:length(p) affectedqubits(m::Union{AbstractMeasurement,sMRX,sMRY,sMRZ}) = (m.qubit,) +affectedqubits(m::AbstractReset) = (m.qubit,) affectedqubits(v::VerifyOp) = v.indices affectedqubits(c::CliffordOperator) = 1:nqubits(c) affectedqubits(c::ClassicalXOR) = () diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index 7cdb614bc..f60aec042 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -215,23 +215,28 @@ end # return backtrajectory(circuit0, nqubits(state)) # end -function backtrajectory(circuit::Vector{<:AbstractOperation}) - n = 0 - for op in circuit - if op isa AbstractSingleQubitOperator - n = max(n, op.q) - elseif op isa AbstractTwoQubitOperator - n = max(n, op.q1, op.q2) - elseif op isa AbstractMeasurement - n = max(n, op.qubit) - elseif op isa AbstractReset - n = max(n, op.qubit) - elseif typeof(op) in [sMRX, sMRY, sMRZ] - n = max(n, op.qubit) - else - error("Unsupported operation: $(typeof(op))") - end - end +# function backtrajectory(circuit::Vector{<:AbstractOperation}) +# n = 0 +# for op in circuit +# if op isa AbstractSingleQubitOperator +# n = max(n, op.q) +# elseif op isa AbstractTwoQubitOperator +# n = max(n, op.q1, op.q2) +# elseif op isa AbstractMeasurement +# n = max(n, op.qubit) +# elseif op isa AbstractReset +# n = max(n, op.qubit) +# elseif typeof(op) in [sMRX, sMRY, sMRZ] +# n = max(n, op.qubit) +# else +# error("Unsupported operation: $(typeof(op))") +# end +# end + +# return backtrajectory(circuit, n) +# end +function backtrajectory(circuit::Vector{<:AbstractOperation}) + n = maximum(Iterators.flatten(affectedqubits.(circuit))) return backtrajectory(circuit, n) end From 1bec6dd4efc10aa705246991dc1a7bbf6c1c80be Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Sat, 26 Jul 2025 02:09:05 +0530 Subject: [PATCH 13/26] naming changes --- src/backtrajectory.jl | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index f60aec042..d58954777 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -44,12 +44,8 @@ function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int) for op in circuit if op isa AbstractCliffordOperator apply_right!(T, op) - elseif op isa sMX - push!(results, do_MX!(T, op)) - elseif op isa sMY - push!(results, do_MY!(T, op)) - elseif op isa sMZ - push!(results, do_MZ!(T, op)) + elseif op isa AbstractMeasurement + push!(results, do_measure!(T, op)) elseif op isa sMRX push!(results, do_MRX!(T, op)) elseif op isa sMRY @@ -67,7 +63,7 @@ function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int) end -function do_MX!(T, op::sMX) +function do_measure!(T, op::sMX) collapse_x!(T, op.qubit) return phases(tab(T))[op.qubit] == 0x00 ? 1 : -1 end @@ -96,7 +92,7 @@ function collapse_x!(T, q::Int) apply_right!(T, sHadamard(q)) end -function do_MY!(T, op::sMY) +function do_measure!(T, op::sMY) collapse_y!(T, op.qubit) return eval_y_obs(T, op.qubit).phase[] == 0x00 ? 1 : -1 end @@ -141,7 +137,7 @@ function eval_y_obs(T, q::Int) return result end -function do_MZ!(T, op::sMZ) +function do_measure!(T, op::sMZ) collapse_z!(T, op.qubit) return phases(tab(T))[nqubits(T)+op.qubit] == 0x00 ? 1 : -1 end @@ -209,6 +205,20 @@ end @inline getzbytes(T, r) = tab(T).xzs[2:2:end,r] +# function do_measure!(T, op::PauliMeasurement) +# h_xz = [] +# h_yz = [] +# cnot = [] +# meas = [] + +# start = 0 + +# end + +# function do_set!(T, state) +# end + + # function backtrajectory(circuit0::Vector{AbstractOperation}, state::AbstractStabilizer) # # TODO - Figure out if to use Reset or Gates # pushfirst!(circuit0, Reset(state, 1:nqubits(state))) From c34c6b401bc5c9d99bb33636d46a42c48a180513 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Sun, 27 Jul 2025 02:24:23 +0530 Subject: [PATCH 14/26] pauli measurements for backtraj --- src/QuantumClifford.jl | 2 +- src/backtrajectory.jl | 112 ++++++++++++++++++-------------------- src/symbolic_cliffords.jl | 30 ++++++++++ 3 files changed, 84 insertions(+), 60 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index e0505305b..91f746202 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -1438,8 +1438,8 @@ include("linalg.jl") include("operator_traits.jl") include("mctrajectory.jl") include("petrajectory.jl") -include("backtrajectory.jl") include("misc_ops.jl") +include("backtrajectory.jl") include("classical_register.jl") include("noise.jl") include("affectedqubits.jl") diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index d58954777..6a7adb1ef 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -1,31 +1,3 @@ -# new symbolics -abstract type AbstractReset <: AbstractOperation end - -"""Reset a qubit to the |+⟩ state. - -See also: [`sMRX`](@ref), [`Reset`](@ref)""" -struct sRX <: AbstractReset - qubit::Int - sRX(q) = if q<=0 throw(NoZeroQubit) else new(q) end -end - -"""Reset a qubit to the |i₊⟩ state. - -See also: [`sMRY`](@ref), [`Reset`](@ref)""" -struct sRY <: AbstractReset - qubit::Int - sRY(q) = if q<=0 throw(NoZeroQubit) else new(q) end -end - -"""Reset a qubit to the |0⟩ state. - -See also: [`sMRZ`](@ref), [`Reset`](@ref)""" -struct sRZ <: AbstractReset - qubit::Int - sRZ(q) = if q<=0 throw(NoZeroQubit) else new(q) end -end - - """ Simulates measurement results of a Clifford circuit acting on an `n`-qubit |0⟩^⊗n state using the stabilizer tableau backtracking method, as described by Gidney (2021). @@ -205,45 +177,67 @@ end @inline getzbytes(T, r) = tab(T).xzs[2:2:end,r] -# function do_measure!(T, op::PauliMeasurement) -# h_xz = [] -# h_yz = [] -# cnot = [] -# meas = [] +function do_measure!(T, op::PauliMeasurement) + if all(iszero.(op.pauli.xz)) + return op.pauli.phase[] & 0x02 == 0x00 ? 1 : -1 + end -# start = 0 + h_xz = [] + h_yz = [] + cnot = [] + meas = 0 + + for q in nqubits(op.pauli) + x, z = op.pauli[q] + if x + if z + push!(h_yz, q) + else + push!(h_xz, q) + end + end -# end + if iszero(meas) + meas = q + else + push!(cnot, (q, meas)) + end + end + @assert meas > 0 + + for q in h_xz + apply_right!(T, sHadamard(q)) + end + for q in h_yz + apply_right!(T, sHadamardYZ(q)) + end + for (q1, q2) in cnot + apply_right!(T, sCNOT(q1, q2)) + end + result = do_measure!(T, sMZ(meas)) + for (q1, q2) in reverse(cnot) + apply_right!(T, sCNOT(q1, q2)) + end + for q in reverse(h_yz) + apply_right!(T, sHadamardYZ(q)) + end + for q in reverse(h_xz) + apply_right!(T, sHadamard(q)) + end + + return result +end # function do_set!(T, state) # end - -# function backtrajectory(circuit0::Vector{AbstractOperation}, state::AbstractStabilizer) -# # TODO - Figure out if to use Reset or Gates -# pushfirst!(circuit0, Reset(state, 1:nqubits(state))) -# return backtrajectory(circuit0, nqubits(state)) +# function do_reset(T, op::Reset) # end -# function backtrajectory(circuit::Vector{<:AbstractOperation}) -# n = 0 -# for op in circuit -# if op isa AbstractSingleQubitOperator -# n = max(n, op.q) -# elseif op isa AbstractTwoQubitOperator -# n = max(n, op.q1, op.q2) -# elseif op isa AbstractMeasurement -# n = max(n, op.qubit) -# elseif op isa AbstractReset -# n = max(n, op.qubit) -# elseif typeof(op) in [sMRX, sMRY, sMRZ] -# n = max(n, op.qubit) -# else -# error("Unsupported operation: $(typeof(op))") -# end -# end - -# return backtrajectory(circuit, n) +# function backtrajectory(circuit::Vector{AbstractOperation}, state::AbstractStabilizer) +# # TODO - Figure out if to use Reset or Gates +# pushfirst!(circuit, Reset(state, 1:nqubits(state))) +# return backtrajectory(circuit, nqubits(state)) # end function backtrajectory(circuit::Vector{<:AbstractOperation}) diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index 97681e7a6..7c4c54ed9 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -651,3 +651,33 @@ sMRZ(i) = sMRZ(i,0) sMRX(i,::Nothing) = sMRX(i,0) sMRY(i,::Nothing) = sMRY(i,0) sMRZ(i,::Nothing) = sMRZ(i,0) + + +############################## +# Resets +############################## +abstract type AbstractReset <: AbstractOperation end + +"""Reset a qubit to the |+⟩ state. + +See also: [`sMRX`](@ref), [`Reset`](@ref)""" +struct sRX <: AbstractReset + qubit::Int + sRX(q) = if q<=0 throw(NoZeroQubit) else new(q) end +end + +"""Reset a qubit to the |i₊⟩ state. + +See also: [`sMRY`](@ref), [`Reset`](@ref)""" +struct sRY <: AbstractReset + qubit::Int + sRY(q) = if q<=0 throw(NoZeroQubit) else new(q) end +end + +"""Reset a qubit to the |0⟩ state. + +See also: [`sMRZ`](@ref), [`Reset`](@ref)""" +struct sRZ <: AbstractReset + qubit::Int + sRZ(q) = if q<=0 throw(NoZeroQubit) else new(q) end +end \ No newline at end of file From 06cdd2600dfb404bf37ae7d8dbf6ecc91f366de7 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Sun, 27 Jul 2025 03:07:36 +0530 Subject: [PATCH 15/26] naming changes --- src/backtrajectory.jl | 45 ++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index 6a7adb1ef..10245ce98 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -17,30 +17,27 @@ function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int) if op isa AbstractCliffordOperator apply_right!(T, op) elseif op isa AbstractMeasurement - push!(results, do_measure!(T, op)) - elseif op isa sMRX - push!(results, do_MRX!(T, op)) - elseif op isa sMRY - push!(results, do_MRY!(T, op)) - elseif op isa sMRZ - push!(results, do_MRZ!(T, op)) + push!(results, do_op!(T, op)) + elseif typeof(op) ∈ [sMRX, sMRY, sMRZ] + push!(results, do_op!(T, op)) elseif op isa AbstractReset - do_reset!(T, op) + do_op!(T, op) else error("Unsupported operation: $(typeof(op))") end end - return results + final_state = apply_inv!(one(Stabilizer, n), T) + return results, final_state end -function do_measure!(T, op::sMX) +function do_op!(T, op::sMX) collapse_x!(T, op.qubit) return phases(tab(T))[op.qubit] == 0x00 ? 1 : -1 end -function do_MRX!(T, op::sMRX) +function do_op!(T, op::sMRX) collapse_x!(T, op.qubit) result = phases(tab(T))[op.qubit] == 0x00 ? 1 : -1 phases(tab(T))[op.qubit] = 0x00 @@ -48,7 +45,7 @@ function do_MRX!(T, op::sMRX) return result end -function do_reset!(T, op::sRX) +function do_op!(T, op::sRX) collapse_x!(T, op.qubit) phases(tab(T))[op.qubit] = 0x00 phases(tab(T))[nqubits(T)+op.qubit] = 0x00 @@ -64,12 +61,12 @@ function collapse_x!(T, q::Int) apply_right!(T, sHadamard(q)) end -function do_measure!(T, op::sMY) +function do_op!(T, op::sMY) collapse_y!(T, op.qubit) return eval_y_obs(T, op.qubit).phase[] == 0x00 ? 1 : -1 end -function do_MRY!(T, op::sMRY) +function do_op!(T, op::sMRY) collapse_y!(T, op.qubit) result = eval_y_obs(T, op.qubit).phase[] == 0x00 ? 1 : -1 if result == -1 @@ -78,7 +75,7 @@ function do_MRY!(T, op::sMRY) return result end -function do_reset!(T, op::sRY) +function do_op!(T, op::sRY) collapse_y!(T, op.qubit) if eval_y_obs(T, op.qubit).phase[] != 0x00 phases(tab(T))[nqubits(T)+op.qubit] ⊻= 0x02 @@ -109,12 +106,12 @@ function eval_y_obs(T, q::Int) return result end -function do_measure!(T, op::sMZ) +function do_op!(T, op::sMZ) collapse_z!(T, op.qubit) return phases(tab(T))[nqubits(T)+op.qubit] == 0x00 ? 1 : -1 end -function do_MRZ!(T, op::sMRZ) +function do_op!(T, op::sMRZ) collapse_z!(T, op.qubit) result = phases(tab(T))[nqubits(T)+op.qubit] == 0x00 ? 1 : -1 phases(tab(T))[op.qubit] = 0x00 @@ -122,7 +119,7 @@ function do_MRZ!(T, op::sMRZ) return result end -function do_reset!(T, op::sRZ) +function do_op!(T, op::sRZ) collapse_z!(T, op.qubit) phases(tab(T))[op.qubit] = 0x00 phases(tab(T))[nqubits(T)+op.qubit] = 0x00 @@ -177,7 +174,7 @@ end @inline getzbytes(T, r) = tab(T).xzs[2:2:end,r] -function do_measure!(T, op::PauliMeasurement) +function do_op!(T, op::PauliMeasurement) if all(iszero.(op.pauli.xz)) return op.pauli.phase[] & 0x02 == 0x00 ? 1 : -1 end @@ -214,7 +211,7 @@ function do_measure!(T, op::PauliMeasurement) for (q1, q2) in cnot apply_right!(T, sCNOT(q1, q2)) end - result = do_measure!(T, sMZ(meas)) + result = do_op!(T, sMZ(meas)) for (q1, q2) in reverse(cnot) apply_right!(T, sCNOT(q1, q2)) end @@ -228,19 +225,15 @@ function do_measure!(T, op::PauliMeasurement) return result end -# function do_set!(T, state) -# end - -# function do_reset(T, op::Reset) +# function do_op!(T, op::Reset) # end # function backtrajectory(circuit::Vector{AbstractOperation}, state::AbstractStabilizer) -# # TODO - Figure out if to use Reset or Gates # pushfirst!(circuit, Reset(state, 1:nqubits(state))) # return backtrajectory(circuit, nqubits(state)) # end function backtrajectory(circuit::Vector{<:AbstractOperation}) - n = maximum(Iterators.flatten(affectedqubits.(circuit))) + n = maximum(Iterators.flatten(affectedqubits.(circuit)); init=1) return backtrajectory(circuit, n) end From 82d5e8d43b188f1c846cf23cbaeba09ccbce0999 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Sun, 27 Jul 2025 03:16:57 +0530 Subject: [PATCH 16/26] remove sSQRTZ, update tests --- src/QuantumClifford.jl | 2 +- test/test_apply_right.jl | 48 ++++++---------------------------------- test/test_inv.jl | 2 +- 3 files changed, 9 insertions(+), 43 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index d11d99720..43460dce4 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -53,7 +53,7 @@ export # Symbolic Clifford Ops AbstractSymbolicOperator, AbstractSingleQubitOperator, AbstractTwoQubitOperator, sHadamard, sPhase, sInvPhase, SingleQubitOperator, sId1, sX, sY, sZ, - sHadamardXY, sHadamardYZ, sSQRTX, sInvSQRTX, sSQRTY, sInvSQRTY, sSQRTZ, sInvSQRTZ, sCXYZ, sCZYX, + sHadamardXY, sHadamardYZ, sSQRTX, sInvSQRTX, sSQRTY, sInvSQRTY, sCXYZ, sCZYX, sCNOT, sCPHASE, sSWAP, sXCX, sXCY, sXCZ, sYCX, sYCY, sYCZ, sZCX, sZCY, sZCZ, sZCrY, sInvZCrY, sSWAPCX, sInvSWAPCX, sCZSWAP, sCXSWAP, sISWAP, sInvISWAP, diff --git a/test/test_apply_right.jl b/test/test_apply_right.jl index 5cf1185a3..31c0a272d 100644 --- a/test/test_apply_right.jl +++ b/test/test_apply_right.jl @@ -15,21 +15,10 @@ l = random_clifford(q) q1 = rand(1:q) - @test isequal(apply_right!(copy(l), sHadamard(q1)), apply_right_slow!(l, sHadamard(q1))) - @test isequal(apply_right!(copy(l), sHadamardXY(q1)), apply_right_slow!(l, sHadamardXY(q1))) - @test isequal(apply_right!(copy(l), sHadamardYZ(q1)), apply_right_slow!(l, sHadamardYZ(q1))) - @test isequal(apply_right!(copy(l), sPhase(q1)), apply_right_slow!(l, sPhase(q1))) - @test isequal(apply_right!(copy(l), sInvPhase(q1)), apply_right_slow!(l, sInvPhase(q1))) - @test isequal(apply_right!(copy(l), sX(q1)), apply_right_slow!(l, sX(q1))) - @test isequal(apply_right!(copy(l), sY(q1)), apply_right_slow!(l, sY(q1))) - @test isequal(apply_right!(copy(l), sZ(q1)), apply_right_slow!(l, sZ(q1))) - @test isequal(apply_right!(copy(l), sSQRTX(q1)), apply_right_slow!(l, sSQRTX(q1))) - @test isequal(apply_right!(copy(l), sInvSQRTX(q1)), apply_right_slow!(l, sInvSQRTX(q1))) - @test isequal(apply_right!(copy(l), sSQRTY(q1)), apply_right_slow!(l, sSQRTY(q1))) - @test isequal(apply_right!(copy(l), sInvSQRTY(q1)), apply_right_slow!(l, sInvSQRTY(q1))) - @test isequal(apply_right!(copy(l), sCXYZ(q1)), apply_right_slow!(l, sCXYZ(q1))) - @test isequal(apply_right!(copy(l), sCZYX(q1)), apply_right_slow!(l, sCZYX(q1))) - @test isequal(apply_right!(copy(l), sId1(q1)), apply_right_slow!(l, sId1(q1))) + for gate in subtypes(AbstractSingleQubitOperator) + gate == SingleQubitOperator && continue + @test isequal(apply_right!(copy(l), gate(q1)), apply_right_slow!(l, gate(q1))) + end end end @@ -38,32 +27,9 @@ l = random_clifford(q) q1 = rand(1:q); q2 = rand(setdiff(1:q, [q1])) - @test isequal(apply_right!(copy(l), sSWAP(q1, q2)), apply_right_slow!(l, sSWAP(q1, q2))) - @test isequal(apply_right!(copy(l), sSWAPCX(q1, q2)), apply_right_slow!(l, sSWAPCX(q1, q2))) - @test isequal(apply_right!(copy(l), sInvSWAPCX(q1, q2)), apply_right_slow!(l, sInvSWAPCX(q1, q2))) - @test isequal(apply_right!(copy(l), sISWAP(q1, q2)), apply_right_slow!(l, sISWAP(q1, q2))) - @test isequal(apply_right!(copy(l), sInvISWAP(q1, q2)), apply_right_slow!(l, sInvISWAP(q1, q2))) - @test isequal(apply_right!(copy(l), sCZSWAP(q1, q2)), apply_right_slow!(l, sCZSWAP(q1, q2))) - @test isequal(apply_right!(copy(l), sCXSWAP(q1, q2)), apply_right_slow!(l, sCXSWAP(q1, q2))) - @test isequal(apply_right!(copy(l), sCNOT(q1, q2)), apply_right_slow!(l, sCNOT(q1, q2))) - @test isequal(apply_right!(copy(l), sCPHASE(q1, q2)), apply_right_slow!(l, sCPHASE(q1, q2))) - @test isequal(apply_right!(copy(l), sZCX(q1, q2)), apply_right_slow!(l, sZCX(q1, q2))) - @test isequal(apply_right!(copy(l), sZCY(q1, q2)), apply_right_slow!(l, sZCY(q1, q2))) - @test isequal(apply_right!(copy(l), sZCZ(q1, q2)), apply_right_slow!(l, sZCZ(q1, q2))) - @test isequal(apply_right!(copy(l), sXCX(q1, q2)), apply_right_slow!(l, sXCX(q1, q2))) - @test isequal(apply_right!(copy(l), sXCY(q1, q2)), apply_right_slow!(l, sXCY(q1, q2))) - @test isequal(apply_right!(copy(l), sXCZ(q1, q2)), apply_right_slow!(l, sXCZ(q1, q2))) - @test isequal(apply_right!(copy(l), sYCX(q1, q2)), apply_right_slow!(l, sYCX(q1, q2))) - @test isequal(apply_right!(copy(l), sYCY(q1, q2)), apply_right_slow!(l, sYCY(q1, q2))) - @test isequal(apply_right!(copy(l), sYCZ(q1, q2)), apply_right_slow!(l, sYCZ(q1, q2))) - @test isequal(apply_right!(copy(l), sZCrY(q1, q2)), apply_right_slow!(l, sZCrY(q1, q2))) - @test isequal(apply_right!(copy(l), sInvZCrY(q1, q2)), apply_right_slow!(l, sInvZCrY(q1, q2))) - @test isequal(apply_right!(copy(l), sSQRTZZ(q1, q2)), apply_right_slow!(l, sSQRTZZ(q1, q2))) - @test isequal(apply_right!(copy(l), sInvSQRTZZ(q1, q2)), apply_right_slow!(l, sInvSQRTZZ(q1, q2))) - @test isequal(apply_right!(copy(l), sSQRTXX(q1, q2)), apply_right_slow!(l, sSQRTXX(q1, q2))) - @test isequal(apply_right!(copy(l), sInvSQRTXX(q1, q2)), apply_right_slow!(l, sInvSQRTXX(q1, q2))) - @test isequal(apply_right!(copy(l), sSQRTYY(q1, q2)), apply_right_slow!(l, sSQRTYY(q1, q2))) - @test isequal(apply_right!(copy(l), sInvSQRTYY(q1, q2)), apply_right_slow!(l, sInvSQRTYY(q1, q2))) + for gate in subtypes(AbstractTwoQubitOperator) + @test isequal(apply_right!(copy(l), gate(q1, q2)), apply_right_slow!(l, gate(q1, q2))) + end end end end \ No newline at end of file diff --git a/test/test_inv.jl b/test/test_inv.jl index 8e8752bb5..20e047f73 100644 --- a/test/test_inv.jl +++ b/test/test_inv.jl @@ -5,7 +5,7 @@ stabilizers = [S" I", S" X", S" Y", S" Z", S"-I", S"-X", S"-Y", S"-Z",] for gate in subtypes(AbstractSingleQubitOperator) - gate ∈ [SingleQubitOperator, sSQRTZ, sInvSQRTZ] && continue + gate == SingleQubitOperator && continue for stab in stabilizers @test apply_inv!(stab, gate(1)) == apply!(stab, inv(CliffordOperator(gate(1), 1))) end From f7fda1bb820418f965649da95cd351b71500f4d8 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Sun, 27 Jul 2025 10:36:37 +0530 Subject: [PATCH 17/26] switch to using registers --- src/backtrajectory.jl | 42 +++++++++++++++++++++------------------ src/classical_register.jl | 4 ++++ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index 10245ce98..15e510776 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -9,17 +9,19 @@ while random measurements are simplified and simulated with randomized gate inse Reference: Gidney, C. (2021). Stim: A fast stabilizer circuit simulator. *Quantum*, 5, 497. https://doi.org/10.22331/q-2021-07-06-497 """ -function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int) +function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int, m::Int=0) T = one(CliffordOperator, n) - results = Int8[] + result = Register(one(Stabilizer, n), falses(m)) for op in circuit if op isa AbstractCliffordOperator apply_right!(T, op) elseif op isa AbstractMeasurement - push!(results, do_op!(T, op)) + res = do_op!(T, op) + op.bit!=0 && (bitview(result)[op.bit] = res) elseif typeof(op) ∈ [sMRX, sMRY, sMRZ] - push!(results, do_op!(T, op)) + res = do_op!(T, op) + op.bit!=0 && (bitview(result)[op.bit] = res) elseif op isa AbstractReset do_op!(T, op) else @@ -27,19 +29,26 @@ function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int) end end - final_state = apply_inv!(one(Stabilizer, n), T) - return results, final_state + apply_inv!(result, T) + return result +end + +function backtrajectory(circuit::Vector{<:AbstractOperation}) + n = maximum(Iterators.flatten(affectedqubits.(circuit)); init=1) + m = maximum(Iterators.flatten(affectedbits.(circuit)); init=0) + return backtrajectory(circuit, n, m) end + function do_op!(T, op::sMX) collapse_x!(T, op.qubit) - return phases(tab(T))[op.qubit] == 0x00 ? 1 : -1 + return phases(tab(T))[op.qubit] != 0x00 end function do_op!(T, op::sMRX) collapse_x!(T, op.qubit) - result = phases(tab(T))[op.qubit] == 0x00 ? 1 : -1 + result = phases(tab(T))[op.qubit] != 0x00 phases(tab(T))[op.qubit] = 0x00 phases(tab(T))[nqubits(T)+op.qubit] = 0x00 return result @@ -63,13 +72,13 @@ end function do_op!(T, op::sMY) collapse_y!(T, op.qubit) - return eval_y_obs(T, op.qubit).phase[] == 0x00 ? 1 : -1 + return eval_y_obs(T, op.qubit).phase[] != 0x00 end function do_op!(T, op::sMRY) collapse_y!(T, op.qubit) - result = eval_y_obs(T, op.qubit).phase[] == 0x00 ? 1 : -1 - if result == -1 + result = eval_y_obs(T, op.qubit).phase[] != 0x00 + if !result phases(tab(T))[nqubits(T)+op.qubit] ⊻= 0x02 end return result @@ -108,12 +117,12 @@ end function do_op!(T, op::sMZ) collapse_z!(T, op.qubit) - return phases(tab(T))[nqubits(T)+op.qubit] == 0x00 ? 1 : -1 + return phases(tab(T))[nqubits(T)+op.qubit] != 0x00 end function do_op!(T, op::sMRZ) collapse_z!(T, op.qubit) - result = phases(tab(T))[nqubits(T)+op.qubit] == 0x00 ? 1 : -1 + result = phases(tab(T))[nqubits(T)+op.qubit] != 0x00 phases(tab(T))[op.qubit] = 0x00 phases(tab(T))[nqubits(T)+op.qubit] = 0x00 return result @@ -176,7 +185,7 @@ end function do_op!(T, op::PauliMeasurement) if all(iszero.(op.pauli.xz)) - return op.pauli.phase[] & 0x02 == 0x00 ? 1 : -1 + return op.pauli.phase[] & 0x02 != 0x00 end h_xz = [] @@ -232,8 +241,3 @@ end # pushfirst!(circuit, Reset(state, 1:nqubits(state))) # return backtrajectory(circuit, nqubits(state)) # end - -function backtrajectory(circuit::Vector{<:AbstractOperation}) - n = maximum(Iterators.flatten(affectedqubits.(circuit)); init=1) - return backtrajectory(circuit, n) -end diff --git a/src/classical_register.jl b/src/classical_register.jl index dbff70d7a..9db140092 100644 --- a/src/classical_register.jl +++ b/src/classical_register.jl @@ -39,6 +39,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) From 848642b445d575a3136b1817ec633115f4eb3ce0 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Tue, 29 Jul 2025 00:53:38 +0530 Subject: [PATCH 18/26] add tests --- test/test_backtraj.jl | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/test/test_backtraj.jl b/test/test_backtraj.jl index 7e6da9070..34aea83b2 100644 --- a/test/test_backtraj.jl +++ b/test/test_backtraj.jl @@ -1,4 +1,27 @@ @testitem "test backtrajectory" begin - @testset "test backtrajectory" begin + + function test_distribution(circuit, num_samples=1000) + results_dict = Dict{Vector{Bool}, Int}() + for _ in 1:num_samples + result = backtrajectory(circuit) + results_dict[bitview(result)] = get(results_dict, bitview(result), 0) + 1 + end + results_dict, quantumstate(backtrajectory(circuit)) end -end \ No newline at end of file + + @testset "circuit 1" begin + circuit = [sHadamard(1), sCNOT(1, 2), sMZ(1, 1), sMZ(2, 2)] + results_dict, final_state = test_distribution(circuit) + + @test haskey(results_dict, Bool[0, 0]) + @test results_dict[Bool[0, 0]] ≈ 500 + @test haskey(results_dict, Bool[1, 1]) + @test results_dict[Bool[1, 1]] ≈ 500 + @test !haskey(results_dict, Bool[0, 1]) + @test !haskey(results_dict, Bool[1, 0]) + end +end + + +# random circuits +# comapre with mctrajectory \ No newline at end of file From 21dd0dd5d7ae0eca30768b30ae25814014da894c Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Thu, 7 Aug 2025 01:50:31 +0530 Subject: [PATCH 19/26] updated function names --- docs/src/references.bib | 12 ++ src/backtrajectory.jl | 346 ++++++++++++++++++++-------------------- 2 files changed, 185 insertions(+), 173 deletions(-) diff --git a/docs/src/references.bib b/docs/src/references.bib index 23ba6e983..9f1cd351d 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -816,3 +816,15 @@ @article{Higgott_2023 year={2023}, month=may } + +@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/backtrajectory.jl b/src/backtrajectory.jl index 15e510776..de78c0c28 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -1,191 +1,114 @@ -""" -Simulates measurement results of a Clifford circuit acting on an `n`-qubit |0⟩^⊗n state using the stabilizer tableau backtracking method, -as described by Gidney (2021). - -This method incrementally folds operations into an identity tableau by prepending inverses of Clifford gates. Pauli-Z measurements are -resolved by transforming their observables to the initial state; deterministic measurements are directly computed from tableau signs, -while random measurements are simplified and simulated with randomized gate insertions. - -Reference: -Gidney, C. (2021). Stim: A fast stabilizer circuit simulator. *Quantum*, 5, 497. https://doi.org/10.22331/q-2021-07-06-497 -""" -function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int, m::Int=0) - T = one(CliffordOperator, n) - result = Register(one(Stabilizer, n), falses(m)) - - for op in circuit - if op isa AbstractCliffordOperator - apply_right!(T, op) - elseif op isa AbstractMeasurement - res = do_op!(T, op) - op.bit!=0 && (bitview(result)[op.bit] = res) - elseif typeof(op) ∈ [sMRX, sMRY, sMRZ] - res = do_op!(T, op) - op.bit!=0 && (bitview(result)[op.bit] = res) - elseif op isa AbstractReset - do_op!(T, op) - else - error("Unsupported operation: $(typeof(op))") - end - end - - apply_inv!(result, T) - return result +struct StimRegister <: AbstractQCState + inv_circuit::CliffordOperator + bits::Vector{Bool} end -function backtrajectory(circuit::Vector{<:AbstractOperation}) - n = maximum(Iterators.flatten(affectedqubits.(circuit)); init=1) - m = maximum(Iterators.flatten(affectedbits.(circuit)); init=0) - return backtrajectory(circuit, n, m) -end +StimRegister(r::StimRegister) = r +StimRegister(qbits::Int, mbits::Int=0) = StimRegister(one(CliffordOperator, qbits), falses(mbits)) +# StimRegister(s::AbstractStabilizer, mbits::Int=0) = StimRegister(..., falses(mbits)) +# StimRegister(r::Register) = StimRegister(..., r.bits) +Base.copy(r::StimRegister) = StimRegister(copy(r.inv_circuit), copy(r.bits)) +Base.:(==)(l::StimRegister,r::StimRegister) = l.inv_circuit==r.inv_circuit && l.bits==r.bits +stabilizerview(r::StimRegister) = stabilizerview(quantumstate(r)) +# destabilizerview(r::StimRegister) = destabilizerview(quantumstate(r)) +# logicalxview(r::StimRegister) = logicalxview(quantumstate(r)) +# logicalzview(r::StimRegister) = logicalzview(quantumstate(r)) -function do_op!(T, op::sMX) - collapse_x!(T, op.qubit) - return phases(tab(T))[op.qubit] != 0x00 -end +nqubits(r::StimRegister) = nqubits(r.inv_circuit) +bitview(r::StimRegister) = r.bits +quantumstate(r::StimRegister) = apply!(one(Stabilizer, nqubits(r)), r.inv_circuit) +tab(r::StimRegister) = tab(r.inv_circuit) -function do_op!(T, op::sMRX) - collapse_x!(T, op.qubit) - result = phases(tab(T))[op.qubit] != 0x00 - phases(tab(T))[op.qubit] = 0x00 - phases(tab(T))[nqubits(T)+op.qubit] = 0x00 - return result -end +tensor(regs::StimRegister...) = StimRegister(tensor([r.inv_circuit for r in regs]), [bit for r in regs for bit in r.bits]) +# tensor(args::AbstractQCState...) = tensor(StimRegister.(args)...) -function do_op!(T, op::sRX) - collapse_x!(T, op.qubit) - phases(tab(T))[op.qubit] = 0x00 - phases(tab(T))[nqubits(T)+op.qubit] = 0x00 +function apply!(r::StimRegister, op, args...; kwargs...) + apply_right!(r.inv_circuit, op, args...; kwargs...) + r end -function collapse_x!(T, q::Int) - if is_deterministic_x(T, q) - return - end - - apply_right!(T, sHadamard(q)) - collapse_z!(T, q) - apply_right!(T, sHadamard(q)) +function apply!(r::StimRegister, m::sMX) + _, res = projectXrand!(r,m.qubit) + m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) + r end - -function do_op!(T, op::sMY) - collapse_y!(T, op.qubit) - return eval_y_obs(T, op.qubit).phase[] != 0x00 +function apply!(r::StimRegister, m::sMY) + _, res = projectYrand!(r,m.qubit) + m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) + r end - -function do_op!(T, op::sMRY) - collapse_y!(T, op.qubit) - result = eval_y_obs(T, op.qubit).phase[] != 0x00 - if !result - phases(tab(T))[nqubits(T)+op.qubit] ⊻= 0x02 - end - return result +function apply!(r::StimRegister, m::sMZ) + _, res = projectZrand!(r,m.qubit) + m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) + r end - -function do_op!(T, op::sRY) - collapse_y!(T, op.qubit) - if eval_y_obs(T, op.qubit).phase[] != 0x00 - phases(tab(T))[nqubits(T)+op.qubit] ⊻= 0x02 - end +function apply!(r::StimRegister, 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 collapse_y!(T, q::Int) - if is_deterministic_y(T, q) - return +function apply!(r::StimRegister, 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 - - apply_right!(T, sHadamardYZ(q)) - collapse_z!(T, q) - apply_right!(T, sHadamardYZ(q)) + r end - -function eval_y_obs(T, q::Int) - result = T[q] - @assert result.phase[] & 0x01 == 0 - og_result_sign = result.phase[] - mul_right!(result, T[nqubits(T)+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 do_op!(T, op::sMZ) - collapse_z!(T, op.qubit) - return phases(tab(T))[nqubits(T)+op.qubit] != 0x00 -end - -function do_op!(T, op::sMRZ) - collapse_z!(T, op.qubit) - result = phases(tab(T))[nqubits(T)+op.qubit] != 0x00 - phases(tab(T))[op.qubit] = 0x00 - phases(tab(T))[nqubits(T)+op.qubit] = 0x00 - return result +function apply!(r::StimRegister, 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 do_op!(T, op::sRZ) - collapse_z!(T, op.qubit) - phases(tab(T))[op.qubit] = 0x00 - phases(tab(T))[nqubits(T)+op.qubit] = 0x00 +function apply!(r::StimRegister, m::PauliMeasurement) + _, res = projectrand!(r,m.pauli) + m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) + r end -function collapse_z!(T, q::Int) - if is_deterministic_z(T, q) - return +function projectXrand!(r::StimRegister, m) + if all(getxrow(tab(r), m) .== 0) + return r, phases(tab(r))[m] end - n = nqubits(T) - t = tab(T) + apply!(r, sHadamard(m)) + collapse_z!(r.inv_circuit, m) + apply!(r, sHadamard(m)) - # 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 + r, phases(tab(r))[m] +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!(T, sCNOT(pivot, k); phases=true) - end +function projectYrand!(r::StimRegister, m) + if all(getxrow(tab(r), m) .== getxrow(tab(r), nqubits(r)+m)) + return r, eval_y_obs(r.inv_circuit, m).phase[] end - # Swap the now-isolated anti-commuting stabilizer generator for one that commutes with the measurement. - if getzbit(t, n+q, pivot) == 0 - apply!(T, sHadamard(pivot); phases=true) - else - apply!(T, sHadamardYZ(pivot); phases=true) - 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 - # Assign a measurement result. - if rand(Bool) #TODO: maybe support a RNG - apply!(T, sX(pivot); phases=true) +function projectZrand!(r::StimRegister, m) + if all(getxrow(tab(r), nqubits(r)+m) .== 0) + return r, phases(tab(r))[nqubits(r)+m] end - return pivot + collapse_z!(r.inv_circuit, m) + + r, phases(tab(r))[nqubits(r)+m] end -@inline is_deterministic_x(T, q::Int) = all(getxbytes(T, q) .== 0) -@inline is_deterministic_y(T, q::Int) = all(getxbytes(T, q) .== getxbytes(T, nqubits(T)+q)) -@inline is_deterministic_z(T, q::Int) = all(getxbytes(T, nqubits(T)+q) .== 0) - -@inline getxbytes(T, r) = tab(T).xzs[1:2:end,r] -@inline getzbytes(T, r) = tab(T).xzs[2:2:end,r] - - -function do_op!(T, op::PauliMeasurement) - if all(iszero.(op.pauli.xz)) - return op.pauli.phase[] & 0x02 != 0x00 +function projectrand!(r::StimRegister, pauli) + if all(iszero.(pauli.xz)) + return pauli.phase[] & 0x02 end h_xz = [] @@ -193,8 +116,8 @@ function do_op!(T, op::PauliMeasurement) cnot = [] meas = 0 - for q in nqubits(op.pauli) - x, z = op.pauli[q] + for q in nqubits(pauli) + x, z = pauli[q] if x if z push!(h_yz, q) @@ -206,38 +129,115 @@ function do_op!(T, op::PauliMeasurement) if iszero(meas) meas = q else - push!(cnot, (q, meas)) + push!(cnot, q) end end @assert meas > 0 for q in h_xz - apply_right!(T, sHadamard(q)) + apply!(r, sHadamard(q)) end for q in h_yz - apply_right!(T, sHadamardYZ(q)) + apply!(r, sHadamardYZ(q)) end - for (q1, q2) in cnot - apply_right!(T, sCNOT(q1, q2)) + for q1 in cnot + apply!(r, sCNOT(q1, meas)) end - result = do_op!(T, sMZ(meas)) - for (q1, q2) in reverse(cnot) - apply_right!(T, sCNOT(q1, q2)) + _, res = projectZrand!(r, meas) + for q1 in reverse(cnot) + apply!(r, sCNOT(q1, meas)) end for q in reverse(h_yz) - apply_right!(T, sHadamardYZ(q)) + apply!(r, sHadamardYZ(q)) end for q in reverse(h_xz) - apply_right!(T, sHadamard(q)) + apply!(r, sHadamard(q)) end - return result + r, res end -# function do_op!(T, op::Reset) +# function traceout!(r::StimRegister, arg) # end + +""" +Simulates measurement results of a Clifford circuit acting on an `n`-qubit |0⟩^⊗n state using the +stabilizer tableau backtracking method [gidney-2021](@cite). + +This method incrementally folds operations into an identity tableau by prepending inverses of +Clifford gates. Pauli-Z measurements are resolved by transforming their observables to the initial +state; deterministic measurements are directly computed from tableau signs,while random measurements +are simplified and simulated with randomized gate insertions. +""" +function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int, m::Int=0) + r = StimRegister(n, m) + for op in circuit + apply!(r, op) + end + r +end +function backtrajectory(circuit::Vector{<:AbstractOperation}) + n = maximum(Iterators.flatten(affectedqubits.(circuit)); init=1) + m = maximum(Iterators.flatten(affectedbits.(circuit)); init=0) + return backtrajectory(circuit, n, m) +end # function backtrajectory(circuit::Vector{AbstractOperation}, state::AbstractStabilizer) # pushfirst!(circuit, Reset(state, 1:nqubits(state))) # return backtrajectory(circuit, nqubits(state)) # 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 From 21031c092fe1a5c9f45afe0ff3dc851abd6adc2b Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Fri, 8 Aug 2025 08:37:54 +0530 Subject: [PATCH 20/26] fix mctrajectory_keepstates counting issue --- src/QuantumClifford.jl | 4 ++-- src/backtrajectory.jl | 40 ++++++++++++++------------------------- src/classical_register.jl | 1 + src/mctrajectory.jl | 3 +-- test/test_backtraj.jl | 25 +----------------------- 5 files changed, 19 insertions(+), 54 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 6be513c12..1f26b1b88 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 @@ -61,7 +61,7 @@ export # Misc Ops SparseGate, sMX, sMY, sMZ, PauliMeasurement, - Reset, sMRX, sMRY, sMRZ, sRX, sRY, sRZ, + Reset, sMRX, sMRY, sMRZ, # sRX, sRY, sRZ, BellMeasurement, ClassicalXOR, VerifyOp, Register, diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index de78c0c28..f2bb62279 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -1,3 +1,12 @@ +""" +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 StimRegister <: AbstractQCState inv_circuit::CliffordOperator bits::Vector{Bool} @@ -10,6 +19,7 @@ StimRegister(qbits::Int, mbits::Int=0) = StimRegister(one(CliffordOperator, qbit Base.copy(r::StimRegister) = StimRegister(copy(r.inv_circuit), copy(r.bits)) Base.:(==)(l::StimRegister,r::StimRegister) = l.inv_circuit==r.inv_circuit && l.bits==r.bits +Base.hash(r::StimRegister, h::UInt) = hash(r.inv_circuit, hash(r.bits, h)) stabilizerview(r::StimRegister) = stabilizerview(quantumstate(r)) # destabilizerview(r::StimRegister) = destabilizerview(quantumstate(r)) @@ -18,7 +28,7 @@ stabilizerview(r::StimRegister) = stabilizerview(quantumstate(r)) nqubits(r::StimRegister) = nqubits(r.inv_circuit) bitview(r::StimRegister) = r.bits -quantumstate(r::StimRegister) = apply!(one(Stabilizer, nqubits(r)), r.inv_circuit) +quantumstate(r::StimRegister) = apply_inv!(one(Stabilizer, nqubits(r)), r.inv_circuit) tab(r::StimRegister) = tab(r.inv_circuit) tensor(regs::StimRegister...) = StimRegister(tensor([r.inv_circuit for r in regs]), [bit for r in regs for bit in r.bits]) @@ -158,33 +168,11 @@ function projectrand!(r::StimRegister, pauli) end # function traceout!(r::StimRegister, arg) + # TODO # end - -""" -Simulates measurement results of a Clifford circuit acting on an `n`-qubit |0⟩^⊗n state using the -stabilizer tableau backtracking method [gidney-2021](@cite). - -This method incrementally folds operations into an identity tableau by prepending inverses of -Clifford gates. Pauli-Z measurements are resolved by transforming their observables to the initial -state; deterministic measurements are directly computed from tableau signs,while random measurements -are simplified and simulated with randomized gate insertions. -""" -function backtrajectory(circuit::Vector{<:AbstractOperation}, n::Int, m::Int=0) - r = StimRegister(n, m) - for op in circuit - apply!(r, op) - end - r -end -function backtrajectory(circuit::Vector{<:AbstractOperation}) - n = maximum(Iterators.flatten(affectedqubits.(circuit)); init=1) - m = maximum(Iterators.flatten(affectedbits.(circuit)); init=0) - return backtrajectory(circuit, n, m) -end -# function backtrajectory(circuit::Vector{AbstractOperation}, state::AbstractStabilizer) -# pushfirst!(circuit, Reset(state, 1:nqubits(state))) -# return backtrajectory(circuit, nqubits(state)) +# function applybranches(r::StimRegister, op) + # TODO # end diff --git a/src/classical_register.jl b/src/classical_register.jl index 9db140092..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)) 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/test/test_backtraj.jl b/test/test_backtraj.jl index 34aea83b2..9275269df 100644 --- a/test/test_backtraj.jl +++ b/test/test_backtraj.jl @@ -1,27 +1,4 @@ @testitem "test backtrajectory" begin - - function test_distribution(circuit, num_samples=1000) - results_dict = Dict{Vector{Bool}, Int}() - for _ in 1:num_samples - result = backtrajectory(circuit) - results_dict[bitview(result)] = get(results_dict, bitview(result), 0) + 1 - end - results_dict, quantumstate(backtrajectory(circuit)) - end - - @testset "circuit 1" begin - circuit = [sHadamard(1), sCNOT(1, 2), sMZ(1, 1), sMZ(2, 2)] - results_dict, final_state = test_distribution(circuit) - @test haskey(results_dict, Bool[0, 0]) - @test results_dict[Bool[0, 0]] ≈ 500 - @test haskey(results_dict, Bool[1, 1]) - @test results_dict[Bool[1, 1]] ≈ 500 - @test !haskey(results_dict, Bool[0, 1]) - @test !haskey(results_dict, Bool[1, 0]) - end + end - - -# random circuits -# comapre with mctrajectory \ No newline at end of file From e3a7c62821bff2aa5a1c620ddc591d4c32d17d65 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Sun, 10 Aug 2025 13:07:31 +0530 Subject: [PATCH 21/26] resets are now measure-reset aliases --- src/QuantumClifford.jl | 2 +- src/symbolic_cliffords.jl | 19 +++---------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 1f26b1b88..ed2be7c38 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -61,7 +61,7 @@ export # Misc Ops SparseGate, sMX, sMY, sMZ, PauliMeasurement, - Reset, sMRX, sMRY, sMRZ, # sRX, sRY, sRZ, + Reset, sMRX, sMRY, sMRZ, sRX, sRY, sRZ, BellMeasurement, ClassicalXOR, VerifyOp, Register, diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index 7c4c54ed9..819354387 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -656,28 +656,15 @@ sMRZ(i,::Nothing) = sMRZ(i,0) ############################## # Resets ############################## -abstract type AbstractReset <: AbstractOperation end """Reset a qubit to the |+⟩ state. - See also: [`sMRX`](@ref), [`Reset`](@ref)""" -struct sRX <: AbstractReset - qubit::Int - sRX(q) = if q<=0 throw(NoZeroQubit) else new(q) end -end +sRX(i) = sMRX(i,0) """Reset a qubit to the |i₊⟩ state. - See also: [`sMRY`](@ref), [`Reset`](@ref)""" -struct sRY <: AbstractReset - qubit::Int - sRY(q) = if q<=0 throw(NoZeroQubit) else new(q) end -end +sRY(i) = sMRY(i,0) """Reset a qubit to the |0⟩ state. - See also: [`sMRZ`](@ref), [`Reset`](@ref)""" -struct sRZ <: AbstractReset - qubit::Int - sRZ(q) = if q<=0 throw(NoZeroQubit) else new(q) end -end \ No newline at end of file +sRZ(i) = sMRZ(i,0) From 1ea084fee959e0e19a6340312493e1cc76e26976 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Sun, 10 Aug 2025 13:07:52 +0530 Subject: [PATCH 22/26] rename to `BacktrackingRegister` --- src/backtrajectory.jl | 61 ++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index f2bb62279..91eca0632 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -7,61 +7,56 @@ simulation by working backwards from measurements to the initial |0⟩^⊗n stat 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 StimRegister <: AbstractQCState +struct BacktrackingRegister <: AbstractQCState inv_circuit::CliffordOperator bits::Vector{Bool} end -StimRegister(r::StimRegister) = r -StimRegister(qbits::Int, mbits::Int=0) = StimRegister(one(CliffordOperator, qbits), falses(mbits)) -# StimRegister(s::AbstractStabilizer, mbits::Int=0) = StimRegister(..., falses(mbits)) -# StimRegister(r::Register) = StimRegister(..., r.bits) +BacktrackingRegister(r::BacktrackingRegister) = r +BacktrackingRegister(qbits::Int, mbits::Int=0) = BacktrackingRegister(one(CliffordOperator, qbits), falses(mbits)) +# BacktrackingRegister(s::AbstractStabilizer, mbits::Int=0) = BacktrackingRegister(..., falses(mbits)) +# BacktrackingRegister(r::Register) = BacktrackingRegister(..., r.bits) -Base.copy(r::StimRegister) = StimRegister(copy(r.inv_circuit), copy(r.bits)) -Base.:(==)(l::StimRegister,r::StimRegister) = l.inv_circuit==r.inv_circuit && l.bits==r.bits -Base.hash(r::StimRegister, h::UInt) = hash(r.inv_circuit, hash(r.bits, h)) +Base.copy(r::BacktrackingRegister) = BacktrackingRegister(copy(r.inv_circuit), copy(r.bits)) +Base.:(==)(l::BacktrackingRegister,r::BacktrackingRegister) = l.inv_circuit==r.inv_circuit && l.bits==r.bits +Base.hash(r::BacktrackingRegister, h::UInt) = hash(r.inv_circuit, hash(r.bits, h)) -stabilizerview(r::StimRegister) = stabilizerview(quantumstate(r)) -# destabilizerview(r::StimRegister) = destabilizerview(quantumstate(r)) -# logicalxview(r::StimRegister) = logicalxview(quantumstate(r)) -# logicalzview(r::StimRegister) = logicalzview(quantumstate(r)) +nqubits(r::BacktrackingRegister) = nqubits(r.inv_circuit) +bitview(r::BacktrackingRegister) = r.bits +quantumstate(r::BacktrackingRegister) = apply_inv!(one(Stabilizer, nqubits(r)), r.inv_circuit) +tab(r::BacktrackingRegister) = tab(r.inv_circuit) -nqubits(r::StimRegister) = nqubits(r.inv_circuit) -bitview(r::StimRegister) = r.bits -quantumstate(r::StimRegister) = apply_inv!(one(Stabilizer, nqubits(r)), r.inv_circuit) -tab(r::StimRegister) = tab(r.inv_circuit) +tensor(regs::BacktrackingRegister...) = BacktrackingRegister(tensor([r.inv_circuit for r in regs]), [bit for r in regs for bit in r.bits]) +# tensor(args::AbstractQCState...) = tensor(BacktrackingRegister.(args)...) -tensor(regs::StimRegister...) = StimRegister(tensor([r.inv_circuit for r in regs]), [bit for r in regs for bit in r.bits]) -# tensor(args::AbstractQCState...) = tensor(StimRegister.(args)...) - -function apply!(r::StimRegister, op, args...; kwargs...) +function apply!(r::BacktrackingRegister, op, args...; kwargs...) apply_right!(r.inv_circuit, op, args...; kwargs...) r end -function apply!(r::StimRegister, m::sMX) +function apply!(r::BacktrackingRegister, m::sMX) _, res = projectXrand!(r,m.qubit) m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) r end -function apply!(r::StimRegister, m::sMY) +function apply!(r::BacktrackingRegister, m::sMY) _, res = projectYrand!(r,m.qubit) m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) r end -function apply!(r::StimRegister, m::sMZ) +function apply!(r::BacktrackingRegister, m::sMZ) _, res = projectZrand!(r,m.qubit) m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) r end -function apply!(r::StimRegister, m::sMRX) +function apply!(r::BacktrackingRegister, 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::StimRegister, m::sMRY) +function apply!(r::BacktrackingRegister, m::sMRY) _, res = projectYrand!(r,m.qubit) m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) if iszero(res) @@ -69,20 +64,20 @@ function apply!(r::StimRegister, m::sMRY) end r end -function apply!(r::StimRegister, m::sMRZ) +function apply!(r::BacktrackingRegister, 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::StimRegister, m::PauliMeasurement) +function apply!(r::BacktrackingRegister, m::PauliMeasurement) _, res = projectrand!(r,m.pauli) m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) r end -function projectXrand!(r::StimRegister, m) +function projectXrand!(r::BacktrackingRegister, m) if all(getxrow(tab(r), m) .== 0) return r, phases(tab(r))[m] end @@ -94,7 +89,7 @@ function projectXrand!(r::StimRegister, m) r, phases(tab(r))[m] end -function projectYrand!(r::StimRegister, m) +function projectYrand!(r::BacktrackingRegister, m) if all(getxrow(tab(r), m) .== getxrow(tab(r), nqubits(r)+m)) return r, eval_y_obs(r.inv_circuit, m).phase[] end @@ -106,7 +101,7 @@ function projectYrand!(r::StimRegister, m) r, eval_y_obs(r.inv_circuit, m).phase[] end -function projectZrand!(r::StimRegister, m) +function projectZrand!(r::BacktrackingRegister, m) if all(getxrow(tab(r), nqubits(r)+m) .== 0) return r, phases(tab(r))[nqubits(r)+m] end @@ -116,7 +111,7 @@ function projectZrand!(r::StimRegister, m) r, phases(tab(r))[nqubits(r)+m] end -function projectrand!(r::StimRegister, pauli) +function projectrand!(r::BacktrackingRegister, pauli) if all(iszero.(pauli.xz)) return pauli.phase[] & 0x02 end @@ -167,11 +162,11 @@ function projectrand!(r::StimRegister, pauli) r, res end -# function traceout!(r::StimRegister, arg) +# function traceout!(r::BacktrackingRegister, arg) # TODO # end -# function applybranches(r::StimRegister, op) +# function applybranches(r::BacktrackingRegister, op) # TODO # end From 7c8a89df874baedd4d8881e6ca8b2b8d7f26c2c7 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Mon, 11 Aug 2025 11:20:39 +0530 Subject: [PATCH 23/26] set initial state --- src/backtrajectory.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index 91eca0632..21d784ccc 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -14,8 +14,8 @@ end BacktrackingRegister(r::BacktrackingRegister) = r BacktrackingRegister(qbits::Int, mbits::Int=0) = BacktrackingRegister(one(CliffordOperator, qbits), falses(mbits)) -# BacktrackingRegister(s::AbstractStabilizer, mbits::Int=0) = BacktrackingRegister(..., falses(mbits)) -# BacktrackingRegister(r::Register) = BacktrackingRegister(..., r.bits) +BacktrackingRegister(s::AbstractStabilizer, mbits::Int=0) = BacktrackingRegister(inv(CliffordOperator(Destabilizer(s))), falses(mbits)) +BacktrackingRegister(r::Register) = BacktrackingRegister(quantumstate(r), r.bits) Base.copy(r::BacktrackingRegister) = BacktrackingRegister(copy(r.inv_circuit), copy(r.bits)) Base.:(==)(l::BacktrackingRegister,r::BacktrackingRegister) = l.inv_circuit==r.inv_circuit && l.bits==r.bits From 1d44505c7bdd500ca8b93241bd1c0f5544a4d7d7 Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Tue, 25 Nov 2025 12:05:41 -0500 Subject: [PATCH 24/26] implement noise, rename to BacktrackRegister --- src/QuantumClifford.jl | 9 +++---- src/affectedqubits.jl | 1 - src/backtrajectory.jl | 56 +++++++++++++++++++++--------------------- src/noise.jl | 39 +++++++++++++++-------------- 4 files changed, 51 insertions(+), 54 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index bba6b8208..d5c85f665 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -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 @@ -64,7 +64,7 @@ export Reset, sMRX, sMRY, sMRZ, sRX, sRY, sRZ, BellMeasurement, ClassicalXOR, VerifyOp, - Register, + Register, BacktrackRegister, # Enumeration and Randoms enumerate_single_qubit_gates, random_clifford1, enumerate_cliffords, symplecticGS, clifford_cardinality, enumerate_phases, @@ -92,8 +92,6 @@ export mctrajectory!, mctrajectories, applywstatus!, # petrajectories petrajectories, applybranches, - # backtrajectory - backtrajectory, # nonclifford GeneralizedStabilizer, UnitaryPauliChannel, PauliChannel, pcT, pcPhase, # makie plotting -- defined only when extension is loaded @@ -1443,8 +1441,8 @@ include("operator_traits.jl") include("mctrajectory.jl") include("petrajectory.jl") include("misc_ops.jl") -include("backtrajectory.jl") include("classical_register.jl") +include("backtrajectory.jl") include("noise.jl") include("affectedqubits.jl") include("pauli_frames.jl") @@ -1468,6 +1466,5 @@ include("grouptableaux.jl") include("plotting_extensions.jl") # include("gpu_adapters.jl") -include("apply_right.jl") end #module diff --git a/src/affectedqubits.jl b/src/affectedqubits.jl index 4d3214d92..3609710e3 100644 --- a/src/affectedqubits.jl +++ b/src/affectedqubits.jl @@ -10,7 +10,6 @@ affectedqubits(n::NoiseOp) = n.indices affectedqubits(g::PauliMeasurement) = 1:length(g.pauli) affectedqubits(p::PauliOperator) = 1:length(p) affectedqubits(m::Union{AbstractMeasurement,sMRX,sMRY,sMRZ}) = (m.qubit,) -affectedqubits(m::AbstractReset) = (m.qubit,) affectedqubits(v::VerifyOp) = v.indices affectedqubits(c::CliffordOperator) = 1:nqubits(c) affectedqubits(c::ClassicalXOR) = () diff --git a/src/backtrajectory.jl b/src/backtrajectory.jl index 21d784ccc..97c9d09f5 100644 --- a/src/backtrajectory.jl +++ b/src/backtrajectory.jl @@ -7,56 +7,56 @@ simulation by working backwards from measurements to the initial |0⟩^⊗n stat 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 BacktrackingRegister <: AbstractQCState +struct BacktrackRegister <: AbstractQCState inv_circuit::CliffordOperator bits::Vector{Bool} end -BacktrackingRegister(r::BacktrackingRegister) = r -BacktrackingRegister(qbits::Int, mbits::Int=0) = BacktrackingRegister(one(CliffordOperator, qbits), falses(mbits)) -BacktrackingRegister(s::AbstractStabilizer, mbits::Int=0) = BacktrackingRegister(inv(CliffordOperator(Destabilizer(s))), falses(mbits)) -BacktrackingRegister(r::Register) = BacktrackingRegister(quantumstate(r), r.bits) +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::BacktrackingRegister) = BacktrackingRegister(copy(r.inv_circuit), copy(r.bits)) -Base.:(==)(l::BacktrackingRegister,r::BacktrackingRegister) = l.inv_circuit==r.inv_circuit && l.bits==r.bits -Base.hash(r::BacktrackingRegister, h::UInt) = hash(r.inv_circuit, hash(r.bits, h)) +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::BacktrackingRegister) = nqubits(r.inv_circuit) -bitview(r::BacktrackingRegister) = r.bits -quantumstate(r::BacktrackingRegister) = apply_inv!(one(Stabilizer, nqubits(r)), r.inv_circuit) -tab(r::BacktrackingRegister) = tab(r.inv_circuit) +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::BacktrackingRegister...) = BacktrackingRegister(tensor([r.inv_circuit for r in regs]), [bit for r in regs for bit in r.bits]) -# tensor(args::AbstractQCState...) = tensor(BacktrackingRegister.(args)...) +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::BacktrackingRegister, op, args...; kwargs...) +function apply!(r::BacktrackRegister, op, args...; kwargs...) apply_right!(r.inv_circuit, op, args...; kwargs...) r end -function apply!(r::BacktrackingRegister, m::sMX) +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::BacktrackingRegister, m::sMY) +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::BacktrackingRegister, m::sMZ) +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::BacktrackingRegister, m::sMRX) +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::BacktrackingRegister, m::sMRY) +function apply!(r::BacktrackRegister, m::sMRY) _, res = projectYrand!(r,m.qubit) m.bit!=0 && (bitview(r)[m.bit] = !iszero(res)) if iszero(res) @@ -64,20 +64,20 @@ function apply!(r::BacktrackingRegister, m::sMRY) end r end -function apply!(r::BacktrackingRegister, m::sMRZ) +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::BacktrackingRegister, m::PauliMeasurement) +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::BacktrackingRegister, m) +function projectXrand!(r::BacktrackRegister, m) if all(getxrow(tab(r), m) .== 0) return r, phases(tab(r))[m] end @@ -89,7 +89,7 @@ function projectXrand!(r::BacktrackingRegister, m) r, phases(tab(r))[m] end -function projectYrand!(r::BacktrackingRegister, m) +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 @@ -101,7 +101,7 @@ function projectYrand!(r::BacktrackingRegister, m) r, eval_y_obs(r.inv_circuit, m).phase[] end -function projectZrand!(r::BacktrackingRegister, m) +function projectZrand!(r::BacktrackRegister, m) if all(getxrow(tab(r), nqubits(r)+m) .== 0) return r, phases(tab(r))[nqubits(r)+m] end @@ -111,7 +111,7 @@ function projectZrand!(r::BacktrackingRegister, m) r, phases(tab(r))[nqubits(r)+m] end -function projectrand!(r::BacktrackingRegister, pauli) +function projectrand!(r::BacktrackRegister, pauli) if all(iszero.(pauli.xz)) return pauli.phase[] & 0x02 end @@ -162,11 +162,11 @@ function projectrand!(r::BacktrackingRegister, pauli) r, res end -# function traceout!(r::BacktrackingRegister, arg) +# function traceout!(r::BacktrackRegister, arg) # TODO # end -# function applybranches(r::BacktrackingRegister, op) +# function applybranches(r::BacktrackRegister, op) # TODO # end 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 Date: Tue, 25 Nov 2025 12:42:59 -0500 Subject: [PATCH 25/26] remove redundant resets --- src/QuantumClifford.jl | 3 +-- src/symbolic_cliffords.jl | 19 +------------------ 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index d5c85f665..13c1fbb34 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -60,8 +60,7 @@ export sSQRTZZ, sInvSQRTZZ, sSQRTXX, sInvSQRTXX, sSQRTYY, sInvSQRTYY, # Misc Ops SparseGate, - sMX, sMY, sMZ, PauliMeasurement, - Reset, sMRX, sMRY, sMRZ, sRX, sRY, sRZ, + sMX, sMY, sMZ, PauliMeasurement, Reset, sMRX, sMRY, sMRZ, BellMeasurement, ClassicalXOR, VerifyOp, Register, BacktrackRegister, diff --git a/src/symbolic_cliffords.jl b/src/symbolic_cliffords.jl index 819354387..5157b6060 100644 --- a/src/symbolic_cliffords.jl +++ b/src/symbolic_cliffords.jl @@ -650,21 +650,4 @@ sMRY(i) = sMRY(i,0) sMRZ(i) = sMRZ(i,0) sMRX(i,::Nothing) = sMRX(i,0) sMRY(i,::Nothing) = sMRY(i,0) -sMRZ(i,::Nothing) = sMRZ(i,0) - - -############################## -# Resets -############################## - -"""Reset a qubit to the |+⟩ state. -See also: [`sMRX`](@ref), [`Reset`](@ref)""" -sRX(i) = sMRX(i,0) - -"""Reset a qubit to the |i₊⟩ state. -See also: [`sMRY`](@ref), [`Reset`](@ref)""" -sRY(i) = sMRY(i,0) - -"""Reset a qubit to the |0⟩ state. -See also: [`sMRZ`](@ref), [`Reset`](@ref)""" -sRZ(i) = sMRZ(i,0) +sMRZ(i,::Nothing) = sMRZ(i,0) \ No newline at end of file From 4b00700274d2f791f0374e9faf02a0eba45494ce Mon Sep 17 00:00:00 2001 From: sagnikpal2004 Date: Tue, 25 Nov 2025 13:43:58 -0500 Subject: [PATCH 26/26] tests - not working --- test/test_backtraj.jl | 70 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/test/test_backtraj.jl b/test/test_backtraj.jl index 9275269df..8bff0b0d6 100644 --- a/test/test_backtraj.jl +++ b/test/test_backtraj.jl @@ -1,4 +1,72 @@ @testitem "test backtrajectory" begin + function test_circuit(circuit) + n=maximum((maximum(affectedqubits(g),init=1) for g in circuit),init=1) + m=maximum((maximum(affectedbits(g),init=0) for g in circuit),init=0) - + reg1 = Register(one(Stabilizer, n), m) + counts1 = mctrajectories(reg1, circuit; trajectories=1000, keepstates=true) + + reg2 = BacktrackRegister(n, m) + counts2 = mctrajectories(reg2, circuit; trajectories=1000, keepstates=true) + + for ((reg1, status1), count1) in counts1 + q1 = quantumstate(reg1) + for ((reg2, status2), count2) in counts2 + q2 = MixedDestabilizer(quantumstate(reg2)) + @assert q1 == q2 + @assert status1 == status2 + @assert isapprox(count2, count1, rtol=0.04) + end + end + end + + @testset "circuit 1" begin + circuit = [ + sHadamard(1), + sCNOT(1, 2), + sMZ(1, 1), + sMZ(2, 2), + ] + test_circuit(circuit) + end + + @testset "circuit 2" begin + circuit = [ + sHadamard(1), + sCNOT(1, 2), + sCNOT(2, 3), + sMZ(1, 1), + sMZ(2, 2), + sMZ(3, 3), + ] + test_circuit(circuit) + end + + @testset "random circuit" begin + using Random + Random.seed!(1234) + n = 5 + m = 3 + len = 20 + gates = [sHadamard, sS, sCNOT, sCZ, sMX, sMY, sMZ] + for _ in 1:3 + circuit = [] + for _ in 1:len + gate = rand(gates) + if gate == sCNOT || gate == sCZ + q1 = rand(1:n) + q2 = rand(1:n) + while q2 == q1 + q2 = rand(1:n) + end + push!(circuit, gate(q1, q2)) + elseif gate == sHadamard || gate == sS || gate == sMX || gate == sMY || gate == sMZ + q = rand(1:n) + b = rand(0:m) + push!(circuit, gate(q, b)) + end + end + test_circuit(circuit) + end + end end